0%

initrdを展開・編集・作成

UbuntuOSベースのシンクライアントを作成していた時、
起動時のスプラッシュロゴ表示や、ファイルシステムの復号化処理を行うため、
initrd内部の書き換えを行っていました

なかなかプライベートで触る事は少ないため、書き残しておきます。

環境

  • Linux Mint 19

initrdとは

Linuxを起動する際に、各ディストリビューションとして動作するために必要なサービスやドライバが含まれたファイル群を固めた物

Linuxが起動する時には、基本的に以下の順序で読み込まれていく

  • 1.UEFI
  • 2.ブートローダ
  • 3.カーネル
  • 4.init

カーネルを読み込み基本的なコマンドを使用できる状態にしてから、initrdをマウント、initrd内部の/initを実行する

Linux起動時に黒画面に出現するログは、大半がinitを実行している時に出ている

手順

initrdを作業ディレクトリにコピーする

/bootフォルダ以下に似たような名前のファイルがあるため、それをコピーしてください

1
2
3
$ mkdir work # 作業フォルダ作成
$ cd work
$ cp /boot/initrd.img-4.15.0-54-generic ./initrd

zcatで復号しつつ、ファイルとして出力

1
2
3
$ mkdir initrdfiles
$ cd initrdfiles
$ zcat ../initrd | cpio -idmv

ファイルを確認

1
2
$ ls
bin conf etc init lib lib64 run sbin scripts usr var

initを見てみる

1
$ vi init

init を見てみると shによって実行できる形で書かれている事がわかります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
#!/bin/sh

# Default PATH differs between shells, and is not automatically exported
# by klibc dash. Make it consistent.
export PATH=/sbin:/usr/sbin:/bin:/usr/bin

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
mkdir -p /var/lock
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t proc -o nodev,noexec,nosuid proc /proc

case " $(cat /proc/cmdline) " in
*\ quiet\ *)
quiet=y
;;
*)
quiet=n
echo "Loading, please wait..."
;;
esac
export quiet

# Note that this only becomes /dev on the real filesystem if udev's scripts
# are used; which they will be, but it's worth pointing out
mount -t devtmpfs -o nosuid,mode=0755 udev /dev
mkdir /dev/pts
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true
mount -t tmpfs -o "noexec,nosuid,size=10%,mode=0755" tmpfs /run
mkdir -m 0755 /run/initramfs

# Export the dpkg architecture
export DPKG_ARCH=
. /conf/arch.conf

# Set modprobe env
export MODPROBE_OPTIONS="-qb"

# Export relevant variables
export ROOT=
export ROOTDELAY=
export ROOTFLAGS=
export ROOTFSTYPE=
export IP=
export IP6=
export BOOT=
export BOOTIF=
export UBIMTD=
export break=
export init=/sbin/init
export readonly=y
export rootmnt=/root
export debug=
export panic=
export blacklist=
export resume=
export resume_offset=
export drop_caps=
export fastboot=n
export forcefsck=n
export fsckfix=
export recovery=
export NETWORK_SKIP_ENSLAVED=


# mdadm needs hostname to be set. This has to be done before the udev rules are called!
if [ -f "/etc/hostname" ]; then
/bin/hostname -F /etc/hostname >/dev/null 2>&1
fi

# Bring in the main config
. /conf/initramfs.conf
for conf in conf/conf.d/*; do
[ -f ${conf} ] && . ${conf}
done
. /scripts/functions

# Parse command line options
for x in $(cat /proc/cmdline); do
case $x in
init=*)
init=${x#init=}
;;
root=*)
ROOT=${x#root=}
if [ -z "${BOOT}" ] && [ "$ROOT" = "/dev/nfs" ]; then
BOOT=nfs
fi
;;
rootflags=*)
ROOTFLAGS="-o ${x#rootflags=}"
;;
rootfstype=*)
ROOTFSTYPE="${x#rootfstype=}"
;;
rootdelay=*)
ROOTDELAY="${x#rootdelay=}"
case ${ROOTDELAY} in
*[![:digit:].]*)
ROOTDELAY=
;;
esac
;;
resumedelay=*)
RESUMEDELAY="${x#resumedelay=}"
;;
loop=*)
LOOP="${x#loop=}"
;;
loopflags=*)
LOOPFLAGS="-o ${x#loopflags=}"
;;
loopfstype=*)
LOOPFSTYPE="${x#loopfstype=}"
;;
cryptopts=*)
cryptopts="${x#cryptopts=}"
;;
nfsroot=*)
NFSROOT="${x#nfsroot=}"
;;
netboot=*)
NETBOOT="${x#netboot=}"
;;
ip=*)
IP="${x#ip=}"
;;
ip6=*)
IP6="${x#ip6=}"
;;
boot=*)
BOOT=${x#boot=}
;;
ubi.mtd=*)
UBIMTD=${x#ubi.mtd=}
;;
resume=*)
RESUME="${x#resume=}"
case $RESUME in
UUID=*)
RESUME="/dev/disk/by-uuid/${RESUME#UUID=}"
esac
;;
resume_offset=*)
resume_offset="${x#resume_offset=}"
;;
noresume)
noresume=y
;;
drop_capabilities=*)
drop_caps="-d ${x#drop_capabilities=}"
;;
panic=*)
panic="${x#panic=}"
case ${panic} in
*[![:digit:].]*)
panic=
;;
esac
;;
ro)
readonly=y
;;
rw)
readonly=n
;;
debug)
debug=y
quiet=n
if [ -n "${netconsole}" ]; then
exec >/dev/kmsg 2>&1
else
exec >/run/initramfs/initramfs.debug 2>&1
fi
set -x
;;
debug=*)
debug=y
quiet=n
set -x
;;
break=*)
break=${x#break=}
;;
break)
break=premount
;;
blacklist=*)
blacklist=${x#blacklist=}
;;
netconsole=*)
netconsole=${x#netconsole=}
[ "x$debug" = "xy" ] && exec >/dev/kmsg 2>&1
;;
BOOTIF=*)
BOOTIF=${x#BOOTIF=}
;;
hwaddr=*)
BOOTIF=${x#hwaddr=}
;;
fastboot|fsck.mode=skip)
fastboot=y
;;
forcefsck|fsck.mode=force)
forcefsck=y
;;
fsckfix|fsck.repair=yes)
fsckfix=y
;;
fsck.repair=no)
fsckfix=n
;;
recovery)
recovery=y
;;
esac
done

# Default to BOOT=local if no boot script defined.
if [ -z "${BOOT}" ]; then
BOOT=local
fi

if [ -n "${noresume}" ] || [ "$RESUME" = none ]; then
export noresume=y
unset resume
else
resume=${RESUME:-}
fi

maybe_break top

# export BOOT variable value for compcache,
# so we know if we run from casper
export BOOT

# Don't do log messages here to avoid confusing graphical boots
run_scripts /scripts/init-top

maybe_break modules
[ "$quiet" != "y" ] && log_begin_msg "Loading essential drivers"
[ -n "${netconsole}" ] && modprobe netconsole netconsole="${netconsole}"
load_modules
[ "$quiet" != "y" ] && log_end_msg

maybe_break premount
[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-premount"
run_scripts /scripts/init-premount
[ "$quiet" != "y" ] && log_end_msg

maybe_break mount
log_begin_msg "Mounting root file system"
# Always load local and nfs (since these might be needed for /etc or
# /usr, irrespective of the boot script used to mount the rootfs).
. /scripts/local
. /scripts/nfs
. /scripts/${BOOT}
parse_numeric ${ROOT}
maybe_break mountroot
mount_top
mount_premount
mountroot
log_end_msg

if read_fstab_entry /usr; then
log_begin_msg "Mounting /usr file system"
mountfs /usr
log_end_msg
fi

# Mount cleanup
mount_bottom
nfs_bottom
local_bottom

maybe_break bottom
[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-bottom"
# We expect udev's init-bottom script to move /dev to ${rootmnt}/dev
run_scripts /scripts/init-bottom
[ "$quiet" != "y" ] && log_end_msg

# Move /run to the root
mount -n -o move /run ${rootmnt}/run

validate_init() {
run-init -n "${rootmnt}" "${1}"
}

# Check init is really there
if ! validate_init "$init"; then
echo "Target filesystem doesn't have requested ${init}."
init=
for inittest in /sbin/init /etc/init /bin/init /bin/sh; do
if validate_init "${inittest}"; then
init="$inittest"
break
fi
done
fi

# No init on rootmount
if ! validate_init "${init}" ; then
panic "No init found. Try passing init= bootarg."
fi

maybe_break init

# don't leak too much of env - some init(8) don't clear it
# (keep init, rootmnt, drop_caps)
unset debug
unset MODPROBE_OPTIONS
unset DPKG_ARCH
unset ROOTFLAGS
unset ROOTFSTYPE
unset ROOTDELAY
unset ROOT
unset IP
unset IP6
unset BOOT
unset BOOTIF
unset UBIMTD
unset blacklist
unset break
unset noresume
unset panic
unset quiet
unset readonly
unset resume
unset resume_offset
unset fastboot
unset forcefsck
unset fsckfix

# Move virtual filesystems over to the real filesystem
mount -n -o move /sys ${rootmnt}/sys
mount -n -o move /proc ${rootmnt}/proc

# Chain to real filesystem
exec run-init ${drop_caps} ${rootmnt} ${init} "$@" ${recovery:+--startup-event=recovery} <${rootmnt}/dev/console >${rootmnt}/dev/console 2>&1
echo "Something went badly wrong in the initramfs."
panic "Please file a bug on initramfs-tools."

またこのinitを実行するタイミングではルート(/)をマウントしていないため、ルート(/)にある/binや/sbinを利用できません。

代わりに、initrd内部の/binや/sbinを利用しています。

そのため、ここにバイナリを配置しておく事で起動中に使用できる様になります。

注意
バイナリの実行のために、別途moduleの追加が必要だったりもします。

initrdを作成する

展開後のファイルが配置されているフォルダで行います。
cpioによるアーカイブ化、gzipによる圧縮を同時に行い元の形式に戻します。

1
$ find | cpio -o -H newc  | gzip -c > ../initrd.img-4.15.0-54-generic

作成されたinitrdを元の/boot/initrdと入れ替えても起動する事が確認できるかと思います。

initrdの編集は以上の操作で行います。

init実行中に処理を止める

/boot/grub/grub.cfgのlinuxオプションが書かれている箇所にbreak=initとオプションを追加する事で処理が停止します。
initrd内のデバッグは、このタイミングで一旦止め、initrd内のリソースを使用したコマンド操作が可能です。
処理を進める場合には、exitコマンドを実行し、続行させる事ができます。

注意
HyperV上に起動したLinuxMint19では、このタイミングではコンソールの描画が行われませんでした。
そのため実機で行う事をおすすめします。