Chapter 4. Debugging Techniques
⚠️
syslogdis considered deprecated and largely replaced byrsyslogd.
4.1 Debugging support in the Kernel
A lot of debugging related kernel configurations are introduced in the book. And followings are my kernel configurations.
| CONFIGURATIONS MENTIONED IN LDD | MY SELECTION |
CONFIG_DEBUG_KERNEL |
Y |
CONFIG_DEBUG_SLAB |
SLAB is deprecated and replaced by SLUB. Hence, CONFIG_SLUB_DEBUG and CONFIG_SLUB_DEBUG_ON is enabled |
CONFIG_DEBUG_PAGEALLOC |
Y |
CONFIG_DEBUG_SPINLOCK |
Y |
CONFIG_DEBUG_SPINLOCK_SLEEP |
not found |
CONFIG_INIT_DEBUG |
not found |
CONFIG_DEBUG_INFO |
Y |
CONFIG_MAGIC_SYSRQ |
Y |
CONFIG_DEBUG_STACKOVERFLOW |
N |
CONFIG_DEBUG_STACK_USAGE |
Y |
CONFIG_KALLSYMS |
Y (by default) |
CONFIG_ACPI_DEBUG |
Y (by default) |
CONFIG_DEBUG_DRIVER |
Y |
CONFIG_SCSI_CONSTANTS |
Y (by default) |
CONFIG_INPUT_EVBUG |
m (by default) |
CONFIG_PROFILING |
Y (by default) |
Please feel free to use .config.debug as your linux .config.
4.2 Debugging by Printing
💡 Full code in Debugging by Printing
The way I enable the debugging options for scull device is a bit different from the one shown in the book. As mentioned in Chapter2, it relies on the kbuild system.
scull/Kconfig
Boolean symbol of CONFIG_DEBUG_SCULL is defined in the Kconfig of scull directory as below.
Note that both CONFIG_DEBUG_KERNEL and CONFIG_SCULL should be properly set to enable the configuration of CONFIG_DEBUG_SCULL
config SCULL
tristate "Scull driver by Gaby, Kim"
depends on LDD
help
This is scull device driver based on Linux Device Drvier, 3rd edition,
compatible with linux 6.7.0, Authored by Gaby, Kim.
config DEBUG_SCULL
bool "Enable debugging features of scull driver by Gaby, Kim"
depends on DEBUG_KERNEL && SCULL
help
To enable debugging features of this driver, choose y here.We can see that make menuconfig shows the required dependencies to set CONFIG_DEBUG_SCULL.

Makefile
When CONFIG_DEBUG_SCULL=y, we have to define the SCULL_DEBUG so that debugging macros in scull.h are activated. It can be done by a simple line of ccflags-$(CONFIG_DEBUG_SCULL) in Makefile.
# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
obj-$(CONFIG_SCULL) += scull.o
scull-y := main.o
ccflags-$(CONFIG_DEBUG_SCULL) := -O -g -DSCULL_DEBUG
...You can find the documents regarding ccflags in https://github.com/torvalds/linux/blob/master/Documentation/kbuild/makefiles.rst#compilation-flags
scull.h
With the SCULL_DEBUG defined, debugging macros in scull.h will be defined upon pre-processing. Note, the PDEBUG is modified so that it also prints out the name of the function through __func__ for verbose debugging messages.
/*
* Macros to help debugging
*/
#undef PDEBUG /* undef it, just in case */
#ifdef SCULL_DEBUG
# ifdef __KERNEL__
/* This one if debugging is on, and kernel space */
# define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: [%s] " fmt, __func__, ## args)
# else
/* This one for user space */
# define PDEBUG(fmt, args...) fprintf(stderr, "[%s] " fmt, __func__, ## args)
# endif
#else
# define PDEBUG(fmt, args...) /* not debugging: nothing */
#endif4.3 Debugging by Querying
💡 Full code in Debugging by Querying
proc filesystem without seq_file interface
A lot of changes have been made into proc filesystem API in modern Linux kernel.
create_proc_read_entry()is replaced withproc_create().remove_proc_entry()is replaced withproc_remove().struct proc_opsis used instead ofstruct file_operationsupon/procfile creation.- Function signature of new
proc_ops.proc_read()differs a lot from the one of originalfile_operations.read().
Other than the changes due to Linux version update, I also slightly refactored some logics of scull_read_procmem().
- use
copy_to_user()insteadsprintf()for safe data transfer scull_read_procmemreturns at most one quantum of data at a time, just like thescull_read.- cautiously monitor the number of bytes copying to user to prevent larger return data than the requested one.
Since the way scull_read_procmem delivers the data to user is much of the same with the one of scull_read, code in original scull_read has been refactored into scull_read_util to minimize the code duplication.
ssize_t scull_read_util(struct scull_dev* dev, char __user *buf, size_t count, loff_t *f_pos)
{
PDEBUG("called with count: %lu, f_pos: %lld\n", count, *f_pos);
// case 1. offset is alreay beyond EOF
if (*f_pos >= dev->size)
return 0;
// case 2. offset is within EOF, while offset + count is beyond EOF
if (*f_pos + count > dev->size)
count = dev->size - *f_pos;
// case 3. offset + count is within EOF, look for the target quantum to copy to user space
int list_node_size = dev->qset * dev->quantum;
// locate the q_set list node
int qset_pointer_offset = *f_pos / list_node_size;
struct scull_qset *target = scull_follow(dev, qset_pointer_offset);
// then the quantum pointer, finally pinpoint the f_pos within the quantum
int quantum_pointer_offset = (*f_pos % list_node_size) / dev->quantum;
int quantum_offset = (*f_pos % list_node_size) % dev->quantum;
if (!target || !target->data || !target->data[quantum_pointer_offset])
return 0;
// read only up to the end of this quantum
count = min(count, (size_t)dev->quantum - quantum_offset);
if (copy_to_user(buf, target->data[quantum_pointer_offset] + quantum_offset, count))
return -EFAULT;
*f_pos += count;
PDEBUG("read %lu bytes\n", count);
return count;
}Our scull_read_procmem simply loops through all the devices and transmits all internal contents until the EOF of last scull device.
/*
* The proc filesystem: function to read and entry
*/
static ssize_t scull_read_procmem(struct file *filp, char __user *buf, size_t count, loff_t *offset)
{
PDEBUG("called with count: %lu, offset: %lld\n", count, *offset);
loff_t device_offset = *offset;
ssize_t len = 0;
for (int i = 0 ; i < scull_nr_devs && len <= count; ++i) {
struct scull_dev *device = &scull_devices[i];
if (down_interruptible(&device->sem))
return -ERESTARTSYS;
// skip to next device if needed
if (device_offset >= device->size) {
device_offset -= device->size;
up(&device->sem);
continue;
}
len = scull_read_util(device, buf, count, &device_offset);
// scull_read_util might return -EFAULT
if (len >= 0) {
*offset += len;
PDEBUG("read %ld bytes in total", len);
}
up(&device->sem);
break;
}
return len;
}
static const struct proc_ops scull_proc_fops = {
.proc_read = scull_read_procmem,
};FYI, the refactored version of scull_read is as follows.
ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
ssize_t retval = 0;
struct scull_dev *dev = filp->private_data;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
retval = scull_read_util(dev, buf, count, f_pos);
up(&dev->sem);
PDEBUG( "read %ld bytes from scull device", retval);
return retval;
}proc filesystem with seq_file interface
Fortunately, seq_file interface didn’t change that much. Our implementation is almost the same with the one in the book.
static void *scull_seq_start(struct seq_file *s, loff_t *pos)
{
if (*pos >= scull_nr_devs)
return NULL;
seq_printf(s, "iterate starts at /device/scull%lld\n", *pos);
return scull_devices + *pos;
}
static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
if (*++pos >= scull_nr_devs)
return NULL;
seq_printf(s, "iterate to /device/scull%lld\n", *pos);
return scull_devices + *pos;
}
static int scull_seq_show(struct seq_file *s, void *v)
{
struct scull_dev *device = v;
if (down_interruptible(&device->sem))
return -ERESTARTSYS;
struct scull_qset *qset = device->data;
for (; qset; qset = qset->next) {
if (!qset->data)
continue;
for (int j = 0; j < device->qset; ++j) {
if (!qset->data[j])
continue;
seq_printf(s, (const char *)qset->data[j]);
}
}
up(&device->sem);
return 0;
}
static void scull_seq_stop(struct seq_file *s, void *v)
{
/* Actually, there's nothing to do here */
}
static const struct seq_operations scull_seq_ops = {
.start = scull_seq_start,
.stop = scull_seq_stop,
.next = scull_seq_next,
.show = scull_seq_show,
};
static int scull_proc_open(struct inode *inode, struct file *filp)
{
return seq_open(filp, &scull_seq_ops);
}
static const struct proc_ops scull_seq_proc_ops = {
.proc_read = seq_read,
.proc_open = scull_proc_open,
.proc_release = seq_release,
.proc_lseek = seq_lseek,
};With proc_ops ready, we can now create or remove the /proc files with following function calls.
static void scull_create_proc(void)
{
scull_proc_entry = proc_create(SCULL_PROC, 0644, NULL, &scull_proc_fops);
if (!scull_proc_entry)
PDEBUG("/proc/%s not created", SCULL_PROC);
scull_seq_proc_entry = proc_create(SCULL_SEQ_PROC, 0644, NULL, &scull_seq_proc_ops);
if (!scull_seq_proc_entry)
PDEBUG("/proc/%s not created", SCULL_SEQ_PROC);
}
static void scull_remove_proc(void)
{
proc_remove(scull_proc_entry);
proc_remove(scull_seq_proc_entry);
}Don’t forget to call these functions upon module loading and unloading!
void scull_cleanup_module(void)
{
...
#ifdef SCULL_DEBUG
scull_remove_proc();
#endif
...
}
static int __init scull_init_module(void)
{
...
#ifdef SCULL_DEBUG
scull_create_proc();
#endif
...
}Test /proc files
write_and_query is a simple shell script to write data into all /dev/scull[0-3] and read both /proc/scullproc and /proc/scullseq
#!/bin/sh
device="/dev/scull"
proc_file="/proc/scullproc"
proc_seq="/proc/scullseq"
for i in 0 1 2 3; do
echo "Hello from scull${i}" | sudo dd of="${device}${i}"
done
read_file() {
file=$1
if [ -e "${file}" ]; then
echo "read from" ${file}
cat ${file}
else
echo "${file} does not exit"
fi
}
read_file "${proc_file}"
read_file "${proc_seq}"
⚠️Remaining sections regarding other debugging techniques are not fully experimented, hence, left undocumented.