Hands-on Projects for the Linux Graphics Subsystem
|
The GNU debugger (gdb), the software Swiss army knife, will be used in the current section to inspect some PCI related variables from the source code of the X Server and then some software tools will be used to verify those variables and as expected see that they are identical.
In order to make the debugging process more effectively the X Server debugging symbols need to be installed in our system. As described in this Ubuntu wiki the package required for Ubuntu is xserver-xorg-core-dbg and the next figure shows a snapshot of the downloading process for this package from the Ubuntu Software Center.
As explained in some X Server debugging sites, for instance the previous Ubuntu link or this Xorg link the debugging should take place remotely from another machine via telnet, ssh, etc. For our example telnet is used. There is another case of hands-on experiments in this site that gdb remotely debugged the X Server, section 3.2.2.5.1.
From a Windows PC, sharing the same wi-fi lan with the Ubuntu laptop, we invoke the telnet tool from the DOS command line as:
telnet 192.168.1.101
where 192.168.1.101 is the IP address of the laptop.
At the login prompt I enter the username and password and then I run gdb with root privilege and for this we use sudo:
sudo gdb
next we enter this time the root password of out system.
gdb in a nutshell
The following gdb commands were used in ther current section:
|
We start with a 'break' gdb command at InitOutput() the function called by main(), which scans the PCI bus:
(gdb) break InitOutput
Since InitOutput() is not defined yet (even Xorg, the process that calls this routine isn't defined yet, we do that latter with the 'file' command) gdb asks the following question:
Make breakpoint pending on future shared library load? (y or [n])
we reply with a 'y'.
Before starting the debugging process it is important to underline that is is necessary to debug a new X Server process, and not the current X Server of the Ubuntu system (with the 'attach' gdb command) because the current X Server instance has already initialised itself and therefore has already passed the 'pci' functions called by InitOutput(), where we need to focus.
For this we specify as executable the Xorg:
(gdb) file /usr/bin/Xorg
however we run the new X Server process (Xorg) from the beginning (we don't attach the current Xorg process) and to avoid conflict with the original server the second Xorg instant uses the display 1 (instead of display 0, that the original X Server uses):
(gdb) run :1
The previous two commands make gdb to start for debugging the Xorg at display 1. The equivalent command line to start the X Server without gdb would be:
sudo /usr/bin/Xorg :1
The reply from the 'run' command was:
Starting program: /usr/bin/Xorg :1
[Thread debugging using libthread_db enabled]
Breakpoint 1, InitOutput (pScreenInfo=0x81feb80, argc=2, argv=0xbffff8d4)
at ../../../../hw/xfree86/common/xf86Init.c:566
566 ../../../../hw/xfree86/common/xf86Init.c: No such file or directory.
in ../../../../hw/xfree86/common/xf86Init.c
As expected a second instant of the X Server runs in the system and to verify this we issue a pgrep command out of the gdb shell, either locally or remotely, as:
pgrep Xorg
or
pgrep X
The reply in our system at this specific time was:
791
1550
where 791 and 1550 the process id of the original X Server and the new X Server respectively.
Since a 'break' preceded the 'run' gdb command, gdb halts at the breakpoint, which was InitOutput(). We find the position at the execution thread of the new Xorg with a backtrace command:
(gdb) bt
The gdb reply was:
#0 InitOutput (pScreenInfo=0x81feb80, argc=2, argv=0xbffff8d4)
at ../../../../hw/xfree86/common/xf86Init.c:566
#1 0x08066bbb in main (argc=2, argv=0xbffff8d4, envp=0xbffff8e0)
at ../../dix/main.c:203
The following image shows the previous contents of the telnet window:
A newer code versionAs a next step we would try to step to FindPCIVideoInfo() in order to inspect the info variable, which reveals some pci info, for instance vendor. Since InitOutput() calls xf86BusProbe(), this xf86PciProbe() and the latter FindPCIVideoInfo() we would try the following instructions to the gdb command line:
From the last response of gdb we find that FindPCIVideoInfo() is not defined. As explained in section 3.2.2.3 "FindPCIVideoInfo() code is boxed in dark gray color because it has become obsolete for newer versions of X11". Actually in the newer Ubuntu version FindPCIVideoInfo() is totally missing. To continue experimenting with gdb we have to move to the newer versions of the Xorg source. The newest release (at the time of writing this page) as we read in the Xorg wiki is X11R7.6. By following the link Source Tar Files for X11R7.6 and then the xserver we download the xorg-server-1.9.3.tar.gz zipped tarball to obtain the xserver's source code. At the next steps we work with this version of source code (leaving at the moment the current thread of the code examined in section 3.2.2.3). As mentioned previously the order of function call main()->InitOutput()->xf86BusProbe()->xf86PciProbe()->FindPCIVideoInfo() becomes now main()->InitOutput()->xf86BusProbe()->xf86PciProbe()->xf86IsPrimaryPci(), where variable info is filled, and then the variable is examined.
|
The next commands we run from gdb are:
(gdb) break xf86BusProbe
(gdb) cont
With the previous commands another breakpoint is set this time at the beginning of xf86BusProbe(), which is called from InitOutput() and then the executable continues until the breakpoint is found.
At this point the screen of the original X Server that runs in the laptop blacks out, which means that the debugging process of the new X Server has affected the original X Server. We can not even use the Ctrl+Alt+F1, Ctrl+Alt+F2, etc key combination to swap to a Linux command-line console. However the Linux system is still up, and we can feel lucky that we chose to handle gdb and other Linux tools remotely, using the Linux shell, offered by the telnet utility. |
We run this break-cont pair, this time for xf86PciProbe() called by xf86BusProbe():
(gdb) break xf86PciProbe
(gdb) cont
We want to proceed to xf86IsPrimaryPci(), called by xf86PciProbe() but this time at the end of this routine. We therefore enter the 'break' command to define the break point, the 'cont' to go to the beginning of xf86IsPrimaryPci() and then the 'finish' to reach just at the point the function returns.
(gdb) break xf86IsPrimaryPci
(gdb) cont
(gdb) finish
At this point, as we see from the xf86PciProbe() source code (newer version) variable info has obtained a value:
void xf86PciProbe(void) { int i = 0, k; int num = 0; struct pci_device *info; struct pci_device_iterator *iter; struct pci_device ** xf86PciVideoInfo = NULL; if (!xf86scanpci()) { xf86PciVideoInfo = NULL; return; } iter = pci_slot_match_iterator_create(& xf86IsolateDevice); while ((info = pci_device_next(iter)) != NULL) { if (PCIINFOCLASSES(info->device_class)) { num++; xf86PciVideoInfo = xnfrealloc(xf86PciVideoInfo, (sizeof(struct pci_device *) * (num + 1))); xf86PciVideoInfo[num] = NULL; xf86PciVideoInfo[num - 1] = info; pci_device_probe(info); #ifdef HAVE_PCI_DEVICE_IS_BOOT_VGA if (pci_device_is_boot_vga(info)) { primaryBus.type = BUS_PCI; primaryBus.id.pci = info; } #endif info->user_data = 0; } } free(iter); /* If we haven't found a primary device try a different heuristic */ if (primaryBus.type == BUS_NONE && num) { for (i = 0; i < num; i++) { uint16_t command; info = xf86PciVideoInfo[i]; pci_device_cfg_read_u16(info, & command, 4); if ((command & PCI_CMD_MEM_ENABLE) && ((num == 1) || IS_VGA(info->device_class))) { if (primaryBus.type == BUS_NONE) { primaryBus.type = BUS_PCI; primaryBus.id.pci = info; } else { xf86Msg(X_NOTICE, "More than one possible primary device found\n"); primaryBus.type ^= (BusType)(-1); } } } } /* Print a summary of the video devices found */ for (k = 0; k < num; k++) { const char *prim = " "; Bool memdone = FALSE, iodone = FALSE; info = xf86PciVideoInfo[k]; if (!PCIALWAYSPRINTCLASSES(info->device_class)) continue; if (xf86IsPrimaryPci(info)) prim = "*"; xf86Msg(X_PROBED, "PCI:%s(%u:%u:%u:%u) %04x:%04x:%04x:%04x ", prim, info->domain, info->bus, info->dev, info->func, info->vendor_id, info->device_id, info->subvendor_id, info->subdevice_id); xf86ErrorF("rev %d", info->revision); for (i = 0; i < 6; i++) { struct pci_mem_region * r = & info->regions[i]; if ( r->size && ! r->is_IO ) { if (!memdone) { xf86ErrorF(", Mem @ "); memdone = TRUE; } else xf86ErrorF(", "); xf86ErrorF("0x%08lx/%ld", (long)r->base_addr, (long)r->size); } } for (i = 0; i < 6; i++) { struct pci_mem_region * r = & info->regions[i]; if ( r->size && r->is_IO ) { if (!iodone) { xf86ErrorF(", I/O @ "); iodone = TRUE; } else xf86ErrorF(", "); xf86ErrorF("0x%08lx/%ld", (long)r->base_addr, (long)r->size); } } if ( info->rom_size ) { xf86ErrorF(", BIOS @ 0x\?\?\?\?\?\?\?\?/%ld", (long)info->rom_size); } xf86ErrorF("\n"); } free(xf86PciVideoInfo); } |
We could next examine variable info and more importantly the members of the pci_device struct that info points. From the PciReworkProposal page we find some useful info about this struct and also its definition:
struct pci_device { uint16_t domain; uint8_t bus; uint8_t dev; uint8_t func; uint16_t vendor_id; uint16_t device_id; uint16_t subvendor_id; uint16_t subdevice_id; uint32_t device_class; struct pci_mem_region regions[6]; pciaddr_t rom_size; int irq; void * user_data; }; |
To find the variable values we use the p (print) gdb command.
Value returned is $1 = 1
The previous output was automatically returned by gdb. Next we find the address of info:
(gdb) p info
gdb replies as:
$2 = (struct pci_device *) 0x820f40c
We print also the vendor_id member of the pci_device struct, info points:
(gdb) p info->vendor_id
The reply in our system was:
$3 = 32902
Decimal number 32902 in hex is 0x8086. If we lookup this hexadecimal value in List of PCI ID's we find that 8086 corresponds to Intel:
8086 Intel Corporation
We try to find also info for the device_id member of pci_device:
(gdb) p info->device_id
The system output becomes:
$4 = 13698
We calculate again the hex value of the variable returned, 0x3582. We lookup again the List of PCI ID's and we find:
3582 82852/855GM Integrated Graphics Device
From the last two values we conclude that the video card of the system that runs the X Server,
is Intel 82852/855GM.
We can verify this outcome using some Linux or Windows PCI tools. We run at the Linux laptop, the system that runs the X Servers lspci, a common pci tool for Linux (see section 4.2.2), which returns the following results:
We notice, at the fourth and fifth lines, as expected the same name for the video card:
00:02.0 VGA compatible controller: Intel Corporation 82852/855GM Integrated Graphics Device (rev 02)
00:02.1 Display controller: Intel Corporation 82852/855GM Integrated Graphics De
vice (rev 02)
Since this laptop dual boots from both Linux and Windows, when I run pcitree, a Windows pci tool, it shows similar results:
As we notice in the right-lower pane of the pcitree window this application shows also the PCI Configuration Space. There are two configuration spaces vor video devices: the VGA compatible controller (00:02.0) and the Display controller (00:02.1). We can instantly locate at the configuration spaces the first four bytes, the values 8086 and 3582, that represent the Vendor ID and the Device ID respectively. They certainly match with the ones found from the gdb variable examination. lspci provides the configuration space of the PCI devices with the x argument:
lspci -x The following figure displays this output. The top two configuration spaces at this figure is for the video device:
To find which one is currently used we look at the memory maps of the X Server at the /proc, entering 791 as the process id of the X Server at the time this test performed:
sudo cat /proc/791/maps
The result is shown in the following image:
As we see at the lower part of the previous figure 00:02.0 is the one currently used.
We could also examine some other members of pci_device or print all member values together using the x (examine) gdb command.
To do this we use the starting address of the info pointer (which points to pci_device struct), returned previously with the 'p info' command to be 0x820f40c.
To find for instance all values of the pci_device members until subdevice_id we print:
(gdb) x /14xb 0x820f40c
Notice that in that case we needed to display 13 bytes but we use 14 because the three uint8_t type member had to be aligned to one byte.
The output was:
0x820f40c: 0x00 0x00 0x00 0x02 0x00 0x00 0x86 0x80
0x820f414: 0x82 0x35 0x3c 0x10 0x84 0x30
The last two-byte value corresponds to subdevice_id. Notice that little-endianess require that 0x84 0x30 is actually value 0x3084. We confirm that calculating subdevice_id as previously:
(gdb) p info->subdevice_id
The reply was:
$5 = 12420
This decimal value is hex value 0x3084.
REFERENCES:
Backtracing
X Troubleshooting-Freeze
X Server Debugging (Xorg)
X Server Debugging (Debian)