BUILDING AND DEBUGGING BSD/OS KERNELS By Keith A. Smith February 11, 1998 This document provides a brief overview of the steps required to build, test, and debug a BSD/OS kernel. These notes are based on BSD/OS 3.1. I will discuss six major topics. 1) The hardware environment 2) The kernel source tree 3) Configuring a kernel 4) Building a kernel 5) Booting a kernel 6) Debugging a kernel For further information, you may find it helpful to refer to the document "Building Kernels on BSD/OS Version 3.0" provides some useful information about the organization of the kernel source tree. It also provides far more information about how to configure a BSD/OS kernel than you are likely to need. 1. The Hardware Environment Ideally, you will have two machines available to you. The "build machine" is a stable machine that you use to develop and build kernels. The build machine(s) should be accessable over the network, so you can log into them remotely to do development work. The second machine is the "test machine." This is a machine that you use to test the kernels that you build. As a result this machine will be rebooted frequently. Nobody should log into a test machine except the person who is currently using it to test kernels. You will need access to the console of the test machine in order to test kernels on it. To facilitate kernel debugging, the build machine should be connected to the test machine via a null modem serial cable. This should connect COM2 (dev/tty01) on the build machine to COM2 on the test machine. Margo or your TFs will inform you of the names and location(s) of the build and test machines available to you. They will also arrange for access to whatever building/lab they live in. 2. The Kernel Source Tree The master kernel source tree lives in /usr/src/sys in BSD systems. You will not be doing your development in this tree. Your TF will tell you where you can find a duplicate of this tree for your own development work. (You will either make a copy of the master tree, or there will be one duplicate tree someplace that you will all do your development in.) "Building Kernels on BSD/OS Version 3.0" descibes the organization of the source tree. The best guide to how the kernel functions is the 4.4 BSD book by Kirk McKusick et al. BSD/OS was derived from 4.4 BSD. 3. Configuring A Kernel In this step, you create a configuration file that describes the configuration of the kernel that you want to build. The system will use this configuration file to create a build directory for you. This directory will contain a customized Makefile and an assortment of source files that implement your configuration. You will probably need to perform this step only once. After you have a build directory, you can build kernels as many times as you wish from that directory. 1) Create a configuration file. A) cd to i386/conf in the kernel source tree. B) The file GENERIC contains a standard kernel configuration. Make a copy of this file, giving it the name you want to use for your configuration. I will use the name "DEMO". C) Edit your new configuration file to get the configuration you want. There are lots of configuration options described in "Building Kernels on BSD/OS Version 3.0." For our purposes, the only thing that is important is to turn on debugging support. Find the following lines: # options KGDB # cross-system kernel debugger # options "KGDBRATE=9600" # remote debugging baud rate # options "KGDBDEV=0x800001" # kgdb device, tty01 # options "KGDBINIT=0" # 1: wait for remote at system boot # KGDB_DEBUG_PANIC option: what to do on panic. 0 = dump core; # 1 = prompt for kgdb or dump core; 2 = wait for kgdb without keyboard prompt. # options "KGDB_DEBUG_PANIC=0" Uncomment each of the "options" lines by removing the leading '#' character. YOu will also want to set KGDB_DEBUG_PANIC to be 1. This will allow you to connect to the system with the debugger after it dies. 2) Create a build directory using your configuration file. Run the following command from the configuration directory (i386/conf). Substitute the name of your configuraiton file for "DEMO". config DEMO 3) Go to the newly created build directory: cd ../../compile/DEMO. This directory will contain a Makefile and a variety of .c and .h files that have been created using your configuration description. Run "make depend". 4. Building A Kernel This is easy. Change directory to your build directory (e.g., compile/DEMO), and type "make". Your first build will take about five minutes. The build produces two files. "bsd" is your new kernel, and "bsd.gdb" is a copy of that kernel that includes the extra debugging information that you will need in order to run gdb on it. 5. Booting A Kernel Log onto the test machine as root. The standard kernel will be installed on this machine as /bsd. Copy your kernel to the root directory, but give it a different name (e.g., /bsd.demo). This is important. By preserving the default /bsd, you know that no matter how broken your test kernel may be, you still have a reliable kernel that you can run on the test machine. Before you shutdown the machine, you will need to figure out what device the root file system is on. Run "df" and look for the root file system: # df Filesystem 512-blocks Used Avail Capacity Mounted on /dev/sd0a 127006 18976 101678 16% / /dev/sd0h 1583870 1182908 321768 79% /usr . . . In this example, the first line describes the root file system. The "root device" is the first three characters of the device name. "sd0" in this case. This means that the root partition is on SCSI disk 0. If it is "wd0" that means that you have an IDE disk. It is possible (but unlikely) that you will see "sd1" or "wd1" here. Next, shutdown the test machine. The following command should do the trick: shutdown -r now "-r" tells the system to reboot automatically after the shutdown is complete. "now" tells the system when to reboot. When the system comes back up, you will see the following messages: Loading /boot Press any key to interrupt boot sequence At this point you have 5 seconds to hit a key. If you do not, the system will boot the default kernel (/bsd). After you press a key, the system will prompt you to enter the name of the kernel you want to boot: Boot: Type in the name of your kernel (e.g. /bsd.demo). Since you interrupted the boot sequence, the system will prompt you for some additional information as it boots. If you let the system boot normally, it will boot into multi-user mode without operator intervention. This system will print a variety of messages as it identifies the hardware in the test machine. Then it will ask you what disk it should use as the root device: root device? Type in the root device you determined above (e.g., sd0). The system will then ask you for the name of the shell that it should run: Enter pathname of shell or RETURN for sh: Just hit the Enter key, and the system will spawn /bin/sh. The system is now running in single user mode. This means that the network has not been initialized, and most of the file systems have not been mounted. To get to multi-user mode, exit the shell. 6. Debugging A Kernel There are a variety of techniques that you can use for debugging a kernel. printf() is implemented inside the kernel, so you can print relevant data from within the kernel. The kernel printf() implementation prints directly to the system console. This means that you can only see 25 lines of text at a time. Be careful where you put a printf() statement in the kernel. If you try to print from a routine that is called frequently (e.g., the clock interrupt), the printing will scroll by to fast for you to read. The prefered method for debugging a kernel is to use gdb. To do this, we run gdb on the build machine, and use it to debug a kernel running on the test machine. gdb communicates with the test kernel over a serial line connecting the two machines. To connect gdb to the test kernel, change directories to your compile directory (e.g., compile/DEMO) and run: gdb bsd.gdb When you get the gdb prompt, type: target remote tty01 This tells gdb to debug a remote process using /dev/tty00. When gdb establishes communication with the test kernel, the test kernel will stop running and wait for instructions from gdb. You can examine data values, set break points, and do all of the other things you're accostomed to doing from gdb. To tell the kernel to resume execution while you are connected with gdb, type "cont". When you are done using gdb, use the "detach" command to tell it to close its connection to the remote machine. This is very important. If you simply quit gdb, or reset the test machine, you may leave the serial line in an inconsistent state. When you detach the test kernel will resume normal execution. If your test kernel panics, it should give you the opportunity to connect with gdb. If you have a kernel that is dieing or misbehaving very early in the boot process, set KGDBINIT=1 in the configuration file. This will tell the kernel to stop running and wait for a gdb connection as early as possible in the boot process.