Patching NetBSD kernel

Goal

In this post I want to see how to build NetBSD kernel from source. To do this I plan to edit the kernel’s code to print a message while NetBSD is booting, compile the kernel and boot it.

Start with source code

As specified in Chapter 32. Obtaining the sources, the source code can be downloaded here.

Let’s work with src.tgz only for now. It will be good enough for kernel compilation. Other archives are additions (like xsrc.tgz for X system or syssrc.tgz for sys utils)

Create /usr/src folder, chown it to yourself and unpack the src.tgz with tar -xzf src.tgz -C /

This part is done.

Patch the kernel

After you boot NetBSD, the entire boot precess output can be inspected by running
dmesg or dmesg | less if you want to scroll it.

Here is part of the output of mine:

[     1.000000] Copyright (c) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
[     1.000000]     2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
[     1.000000]     2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023,
[     1.000000]     2024, 2025, 2026
[     1.000000]     The NetBSD Foundation, Inc.  All rights reserved.
[     1.000000] Copyright (c) 1982, 1986, 1989, 1991, 1993
[     1.000000]     The Regents of the University of California.  All rights reserved.

[     1.000000] NetBSD 11.0 (GENERIC) #0: Thu Jul 30 15:23:12 UTC 2026
[     1.000000] 	mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd64/compile/GENERIC
[     1.000000] total memory = 8188 MB
[     1.000000] avail memory = 7890 MB
[     1.000000] timecounter: Timecounters tick every 10.000 msec
[     1.000000] Kernelized RAIDframe activated
[     1.000000] timecounter: Timecounter "i8254" frequency 1193182 Hz quality 100
[     1.000004] efi: systbl at pa bfbee018
[     1.000004] mainbus0 (root)
[     1.000004] ACPI: RSDP 0x00000000BFBFA014 000024 (v02 BHYVE )
[     1.000004] ACPI: XSDT 0x00000000BFBF90E8 00005C (v01 BHYVE  BVFACP   00000001      01000013)
[     1.000004] ACPI: FACP 0x00000000BFBF8000 000114 (v05 BHYVE  BVFACP   00000001 BASL 20220504)
[     1.000004] ACPI: DSDT 0x00000000BE74C000 0009F9 (v02 BHYVE  BVDSDT   00000001 INTL 20250807)
[     1.000004] ACPI: FACS 0x00000000BFBFE000 000040
[     1.000004] ACPI: APIC 0x00000000BFBF7000 000082 (v01 BHYVE  BVAPIC   00000001 BASL 20220504)
[     1.000004] ACPI: HPET 0x00000000BFBF6000 000038 (v01 BHYVE  BVHPET   00000001 BASL 20220504)
[     1.000004] ACPI: MCFG 0x00000000BFBF5000 00003C (v01 BHYVE  BVMCFG   00000001 BASL 20220504)
[     1.000004] ACPI: SPCR 0x00000000BFBF4000 000058 (v01 BHYVE  BVSPCR   00000001 BASL 20220504)
[     1.000004] ACPI: SRAT 0x00000000BFBF3000 000130 (v01 BHYVE  BVSRAT   00000001 BASL 20220504)
[     1.000004] ACPI: BGRT 0x00000000BE2CA000 000038 (v01 INTEL  EDK2     00000002      01000013)
[     1.000004] ACPI: 1 ACPI AML tables successfully acquired and loaded
[     1.000004] ioapic0 at mainbus0 apid 0: pa 0xfec00000, version 0x11, 32 pins
[     1.000004] cpu0 at mainbus0 apid 0
[     1.000004] cpu0: Use mfence to serialize rdtsc
[     1.000004] cpu0: AMD Ryzen 5 7430U with Radeon Graphics         , id 0xa50f00
[     1.000004] cpu0: node 0, package 0, core 0, smt 0
[     1.000004] cpu1 at mainbus0 apid 1

You can see output of some drivers like:

  • ACPI: RSDP 0x00000000BFBFA014 000024 (v02 BHYVE )
  • cpu0: Use mfence to serialize rdtsc

But even before that, there is this message:

[     1.000000] total memory = 8188 MB

Let’s find out where it comes from and print our own message near it.

# run this in the /usr/src
grep -ri 'total memory = ' .

and here is the output I got:

./sys/arch/atari/atari/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/emips/emips/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/evbppc/virtex/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/ews4800mips/ews4800mips/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/hp300/hp300/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/hpcsh/hpcsh/machdep.c:	_DPRINTF("total memory = %dMbyte\n", (int)(sh3_ptob(physmem) >> 20));
./sys/arch/luna68k/luna68k/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/mac68k/mac68k/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/mips/mips/cpu_subr.c:	printf("total memory = %s\n", pbuf);
./sys/arch/mvme68k/mvme68k/machdep.c:	printf("total memory = %s", pbuf);
./sys/arch/news68k/news68k/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/next68k/next68k/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/powerpc/booke/booke_machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/powerpc/ibm4xx/ibm4xx_machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/powerpc/oea/oea_machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/riscv/riscv/riscv_machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/sh3/sh3/sh3_machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/sgimips/sgimips/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/sparc/sparc/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/sparc64/sparc64/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/sun2/sun2/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/sun3/sun3/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/sun3/sun3x/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/vax/vax/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/virt68k/virt68k/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/arch/x68k/x68k/machdep.c:	printf("total memory = %s\n", pbuf);
./sys/kern/init_main.c:	(*pr)("total memory = %s\n", pbuf);
./sys/rump/librump/rumpkern/vm.c:	aprint_verbose("total memory = %s\n", buf);

The ./sys/arch is architecture specific code. I’m on AMD64 architecture and I don’t see it among the output. I don’t know why and it’s something to explore later but this line is interesting:
./sys/kern/init_main.c: (*pr)(“total memory = %s\n”, pbuf);

Looks like something kernel related. Let’s open it.

void
banner(void)
{
	static char notice[] = " Notice: this software is "
	    "protected by copyright";
	char pbuf[81];
	void (*pr)(const char *, ...) __printflike(1, 2);
	int i;

	if ((boothowto & AB_SILENT) != 0) {
		snprintf(pbuf, sizeof(pbuf), "%s %s (%s)",
		    ostype, osrelease, kernel_ident);
		printf_nolog("%s", pbuf);
		for (i = 80 - strlen(pbuf) - sizeof(notice); i > 0; i--)
			printf(" ");
		printf_nolog("%s\n", notice);
		pr = aprint_normal;
	} else {
		pr = printf;
	}

	memset(pbuf, 0, sizeof(pbuf));
	(*pr)("%s%s", copyright, version);
	format_bytes(pbuf, MEM_PBUFSIZE, ctob((uint64_t)physmem));
	(*pr)("total memory = %s\n", pbuf);
	format_bytes(pbuf, MEM_PBUFSIZE, ctob((uint64_t)uvm_availmem(false)));
	(*pr)("avail memory = %s\n", pbuf);
}

The function is called banner which I assume is the banner that we saw during the boot process. It checks out.

The syntax of the print function is quite interesting (*pr)(“total memory = %s\n”, pbuf); but it’s outside of scope of this post. We’ll have a look at it in another post.

Let’s just add one more line at the end of this banner function:

(*pr)("You can create art and beauty on this computer\n");

Save. Done!

Compile and boot the kernel

Now being in /usr/src folder run this command (needed only once):

./build.sh -U -j6 tools # adjust -j6 to your number of vcpu. It's similar to "make"

This may take awhile. NetBSD tools are essential component of reproducible builds and includes compiler which must be built from source too. Again, run only once and forget about it.

If you see errors about /usr/obj not found, create the folder and chown it to yourself.

When done, we can compile the kernel:

./build.sh -U -j6 kernel=GENERIC # -U is for "build as current user not root"

This will start the compilation. GENERIC is the name of the kernel config file in /usr/src/sys/arch/amd64/conf/. You can create your own kernel config and pass it to ./build.sh script.

Kernel compilation is relatively quick and when done you can see an output like this:

===> Kernels built from GENERIC:
  /usr/src/sys/arch/amd64/compile/obj/GENERIC/netbsd
===> build.sh ended:      Wed Sep 23 18:30:32 AEST 2026
===> Summary of results:
	 build.sh command:    ./build.sh -U -j6 kernel=GENERIC
	 build.sh started:    Wed Sep 23 18:26:47 AEST 2026
	 NetBSD version:      11.0
	 MACHINE:             amd64
	 MACHINE_ARCH:        x86_64
	 Build platform:      NetBSD 11.0 amd64
	 HOST_SH:             /bin/sh
	 share/mk MAKECONF:   /etc/mk.conf
	 MAKECONF file:       /etc/mk.conf (File not found)
	 TOOLDIR path:        /usr/src/obj/tooldir.NetBSD-11.0-amd64
	 DESTDIR path:        /usr/src/obj/destdir.amd64
	 RELEASEDIR path:     /usr/src/obj/releasedir
	 Updated makewrapper: /usr/src/obj/tooldir.NetBSD-11.0-amd64/bin/nbmake-amd64
	 Building kernel without building new tools
	 Building kernel:     GENERIC
	 Build directory:     /usr/src/sys/arch/amd64/compile/obj/GENERIC
	 Kernels built from GENERIC:
	  /usr/src/sys/arch/amd64/compile/obj/GENERIC/netbsd
	 build.sh ended:      Wed Sep 23 18:30:32 AEST 2026

The interesting part is:

Kernels built from GENERIC:
  /usr/src/sys/arch/amd64/compile/obj/GENERIC/netbsd

This is our new kernel. Let’s copy it to the root folder under a different name just in case.

cp /usr/src/sys/arch/amd64/compile/obj/GENERIC/netbsd /netbsd.test # as root
# /netbsd is already there and that's the kernel that system boots by default.

All done. Now what’s left is to reboot and boot our custom kernel.

Restart and press SPACE at the boot screen to stop the countdown.

Then press 3 to drop to the boot prompt.

Now just type boot /netbsd.test and observe the output!

[     1.000000] Copyright (c) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
[     1.000000]     2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
[     1.000000]     2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023,
[     1.000000]     2024, 2025, 2026
[     1.000000]     The NetBSD Foundation, Inc.  All rights reserved.
[     1.000000] Copyright (c) 1982, 1986, 1989, 1991, 1993
[     1.000000]     The Regents of the University of California.  All rights reserved.

[     1.000000] NetBSD 11.0 (GENERIC) #1: Wed Sep 23 18:51:29 AEST 2026
[     1.000000]         krydos@netbsdvm:/usr/obj/sys/arch/amd64/compile/GENERIC
[     1.000000] total memory = 8188 MB
[     1.000000] avail memory = 7890 MB
[     1.000000] You can create art and beauty on this computer   <---- here!!!
[     1.000000] timecounter: Timecounters tick every 10.000 msec
[     1.000000] Kernelized RAIDframe activated
[     1.000000] timecounter: Timecounter "i8254" frequency 1193182 Hz quality 100
[     1.000004] efi: systbl at pa bfbee018
[     1.000004] mainbus0 (root)
[     1.000004] ACPI: RSDP 0x00000000BFBFA014 000024 (v02 BHYVE )
[     1.000004] ACPI: XSDT 0x00000000BFBF90E8 00005C (v01 BHYVE  BVFACP   00000001      01000013)
[     1.000004] ACPI: FACP 0x00000000BFBF8000 000114 (v05 BHYVE  BVFACP   00000001 BASL 20220504)
[     1.000004] ACPI: DSDT 0x00000000BE74C000 0009F9 (v02 BHYVE  BVDSDT   00000001 INTL 20250807)
[     1.000004] ACPI: FACS 0x00000000BFBFE000 000040
[     1.000004] ACPI: APIC 0x00000000BFBF7000 000082 (v01 BHYVE  BVAPIC   00000001 BASL 20220504)
[     1.000004] ACPI: HPET 0x00000000BFBF6000 000038 (v01 BHYVE  BVHPET   00000001 BASL 20220504)
[     1.000004] ACPI: MCFG 0x00000000BFBF5000 00003C (v01 BHYVE  BVMCFG   00000001 BASL 20220504)
[     1.000004] ACPI: SPCR 0x00000000BFBF4000 000058 (v01 BHYVE  BVSPCR   00000001 BASL 20220504)
[     1.000004] ACPI: SRAT 0x00000000BFBF3000 000130 (v01 BHYVE  BVSRAT   00000001 BASL 20220504)
[     1.000004] ACPI: BGRT 0x00000000BE2CA000 000038 (v01 INTEL  EDK2     00000002      01000013)
[     1.000004] ACPI: 1 ACPI AML tables successfully acquired and loaded
[     1.000004] ioapic0 at mainbus0 apid 0: pa 0xfec00000, version 0x11, 32 pins
[     1.000004] cpu0 at mainbus0 apid 0
[     1.000004] cpu0: Use mfence to serialize rdtsc
[     1.000004] cpu0: AMD Ryzen 5 7430U with Radeon Graphics         , id 0xa50f00
[     1.000004] cpu0: node 0, package 0, core 0, smt 0
[     1.000004] cpu1 at mainbus0 apid 1

Woohoo!


Send me an email and we can talk about this post.