Monday, November 9, 2009

How to Get the Version of a Kernel Without Booting It

Sometimes you need to get version from a kernel file lying on filesystem and the compiler that was used to compile it, but there is no opportunity to boot from this kernel. For example, once I recompiled my own kernel with some options in .config changed, and I wanted to be sure that new kernel's vermagic string doesn't differ from modules' one. This string is usually being printed by Linux kernel in the beginning of boot, e.g.:
Linux version 2.6.24-19-generic (buildd@terranova) (gcc version 4.2.3 (Ubuntu 4.2.3-2ubuntu7)) #1 SMP Fri Jul 11 23:41:49 UTC 2008 (Ubuntu 2.6.24-19.36-generic)

To know more about vermagic please read chapter 2.8 of The Linux Kernel Module Programming Guide and description of --force-vermagic option in modprobe (8).

To get the vermagic string of some module on a disk, use modinfo module.ko (even if this module was built for other arch, e.g. we work in i386 and modules is for x86_64, and vice versa). But you can not use this trick (or invent a similar one) with a kernel file because modinfo doesn't work with vmlinuz.

But it's not so hard as you can imagine :)

vmlinuz consists of some parts: loader, decompressor and the compressed (zlib) kernel itself. Gzipped content usually begins with byte sequence 1f 8b 08 00. We can use od utility to find them:
od -A d -t x1 vmlinuz|grep "1f 8b 08 00"

But often these bytes may occur in the different lines, like in the following fragment (was taken from Ubuntu Hardy's generic kernel):
0011376 00 fd f3 a4 fc 5e 81 c5 ff ff 0f 00 81 e5 00 00
0011392 f0 ff 8d 83 90 01 1d 00 ff e0 01 01 1d 00 1f 8b
0011408 08 00 26 a1 ac 48 02 03 ec 3a 6d 74 14 55 96 af
0011424 3b d5 49 77 e8 58 15 a7 5b 5b b6 19 1a 2d 30 19

So we will use an alternate method.

Kernel source includes utility named scripts/binoffset.c that does exactly what we need: find the first occurrence of given bytes in the bytes stream. This utility's source is included in the Debian package linux-headers, so you don't need to grab and extract full kernel source.

Let's use it:
$ gcc -o binoffset /usr/src/linux/scripts/binoffset.c
$ ./binoffset /vmlinuz 0x1f 0x8b 0x08 00 2>/dev/null

It means that gzip header begins at offset 11406 from the beginning of the file.

Now extract the compressed kernel from vmlinuz and decompress it:
dd if=/vmlinuz skip=11406 bs=1|gzip -d >vmlinux

And find our string there, using strings + grep:
$ strings vmlinux |grep gcc
Linux version 2.6.24-19-generic (buildd@terranova) (gcc version 4.2.3 (Ubuntu 4.2.3-2ubuntu7)) #1 SMP Wed Aug 20 22:56:21 UTC 2008 (Ubuntu 2.6.24-19.41-generic)
%s version %s (buildd@terranova) (gcc version 4.2.3 (Ubuntu 4.2.3-2ubuntu7)) %s

So, we have got what we want :) Let's concatenate all this stuff to one line:
(dd if=/vmlinuz skip=`./binoffset /vmlinuz 0x1f 0x8b 0x08 0x00` bs=1|gzip -d |strings|grep -n 1"^Linux version") 2>/dev/null

P.S. /usr/src/linux/scripts/extract-ikconfig works like described here, but it needs some modifications to make it do the thing we want... and I like to reinvent the bicycles :)