vmlinuzからvmlinuxへ変換する

convert vmlinuz to vmlinux !

以前、vmlinuzからvmlinuxを抽出する方法を調べたのだが、ここにメモしていなかったため、今回また最初から調べなければならなかった。今回はメモしておく。

具体的な方法は“convert vmlinuz to vmlinux”に書いてあったのだが、実はこの方法、ちょっと難がある。vmlinuzを16進ダンプしてgzipのヘッダである'1f 8b 08 00'がある個所を見つけだしているのだが(その場所から後ろがgzipで圧縮されたvmlinuxの本体)、この4バイトがダンプの1行に入っているとは限らない。2行に渡っていることだってある。実際、今回Chrome OSのvmlinuxを調べようとしたら、

0012912 00 01 8d 83 40 1a 29 00 ff e0 be 19 29 00 1f 8b
0012928 08 00 ad b3 05 4b 02 03 ec 3b 6d 74 53 c7 95 23

となっていて、最初見つからなかった。ちょっとハマって来て“Vmlinux - Wikipedia”に“since Linux 2.6.30, using LZMA or BZIP2”とか書いてあり、Chrome OSカーネルはまさに2.6.30だからgzip圧縮ではなくbzip2圧縮なのかとbzip2のヘッダを探したりして時間を喰った。(しかし、Chrome OSのvmlinuzは結局gzip圧縮だった。また、Ubuntu 9.10で使っている2.6.31でもgzip圧縮だった。)

さて、このような問題を回避するために、次のようにコマンドを組み合わせた。

dd if=vmlinuz bs=1 skip=`grep -abo -e \`printf "\x1f\x8b\x08\x00"\` vmlinuz | awk -F: '{print $1}'` | zcat -d > vmlinux

grepの引数で16進や8進でパターンを指定できないので、こういったちょっとヤヤコシイ組み合わせとなってしまった。

【変更:2010/06/23】
dd if=vmlinuz bs=1 skip=`grep -abo -e \`echo | sed -e "s/^/\x1f\x8b\x08\x00/"\` vmlinuz | awk -F: '{print $1}'` | zcat -d > vmlinux から変更

【追記:2010/06/23】
grep-2.5.1だと、-oを付けても-bは行頭のオフセットを出してしまうので、これだとダメぽ” というツブヤキを見つけたので試したみた。2.5.1は随分前のバージョン(とっていも2年位前?)なのだが、2.5.1ではgrepの仕様なのかバグなのか、確かに-bオプションがパターンマッチしたオフセットではなく、パターンマッチした行の行頭のオフセットを出してしまうので、この方法は使えない。(たまたま私の使っているプロバイダのサーバがgrep 2.5.1だったので確認できた。)2.5.3以降であれば大丈夫。(2.5.2は手元に無かったので試していない。)

実際に動かすと次のようになる。

root@ubuntu:/tmp# cp /vmlinuz vmlinuz
root@ubuntu:/tmp# dd if=vmlinuz bs=1 skip=`grep -abo -e \`printf "\x1f\x8b\x08\x00"\` vmlinuz | awk -F: '{print $1}'` | zcat -d > vmlinux
2708962+0 records in
2708962+0 records out
2708962 bytes (2.7 MB) copied, 2.65972 s, 1.0 MB/s

gzip: stdin: decompression OK, trailing garbage ignored
root@ubuntu:/tmp# file vmlinux
vmlinux: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped
root@ubuntu:/tmp# hd vmlinux | head -2
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 03 00 01 00 00 00  00 00 00 01 34 00 00 00  |............4...|
root@ubuntu:/tmp# readelf -h vmlinux
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x1000000
  Start of program headers:          52 (bytes into file)
  Start of section headers:          5201520 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         3
  Size of section headers:           40 (bytes)
  Number of section headers:         36
  Section header string table index: 35
root@ubuntu:/tmp#

おや?

Chrome OSのvmlinuzは正常にvmlinuxに変換できたが、普段使っている Ubuntu 8.04.3のマシンで実行するちょっと変だ。カーネルはvmlinuz-2.6.24-25-server。

root@vmhost:/tmp# cp /vmlinuz .
root@vmhost:/tmp# dd if=vmlinuz bs=1 skip=`grep -abo -e \`printf "\x1f\x8b\x08\x00"\` vmlinuz | awk -F: '{print $1}'` | zcat -d > vmlinux
1921388+0 records in
1921388+0 records out
1921388 bytes (1.9 MB) copied, 1.26134 s, 1.5 MB/s

gzip: stdin: decompression OK, trailing garbage ignored
root@vmhost:/tmp# file vmlinux
vmlinux: data
root@vmhost:/tmp# hd vmlinux | head -2
00000000  48 8d 2d f9 ff ff ff 48  81 ed 00 00 20 00 48 89  |H.-....H.... .H.|
00000010  e8 25 ff ff 1f 00 85 c0  0f 85 9a 01 00 00 48 8d  |.%............H.|
root@vmhost:/tmp# readelf -h vmlinux
readelf: Error: Unable to read in 0x4800 bytes of section headers
readelf: Error: Not an ELF file - it has the wrong magic bytes at the start
root@vmhost:/tmp#

問題なくgunzipは出来るのだが、解凍されたファイルがELF、COFF、a.outの何れの形式でない。カーネル2.6.24ではx86amd64でも同じような結果になる。

ただ、vmlinuzをvmlinuxに変換した目的はvmlinuxのオブジェクトの内部にある文字列を調べたかっただけで、例えば、

root@vmhost:/tmp# strings vmlinux | grep "Linux version"
Linux version 2.6.24-25-server (buildd@crested) (gcc version 4.2.4 (Ubuntu 4.2.4-1ubuntu3)) #1 SMP Tue Oct 20 07:20:02 UTC 2009 (Ubuntu 2.6.24-25.63-server)
root@vmhost:/tmp#

カーネル内の文字列を抽出はできるので、これ以上の深追いは止めておこう。