17

I'm trying to debug an init script on a Linux system; I'm trying to pass init=/bin/sh to the kernel to make it start sh without starting init so I can run through the init sequence manually.

What I've found is that the kernel is starting init anyway. During bootup, one of the printk messages is the command line, and that is showing that the line is being set properly; in addition, I can affect other things using the kernel command line. I have checked to make sure the path exists; it does.

This is a busybox system, and init is a symlink to busybox; so to make sure busybox doesn't do strange magic when its PID is 1, I also tried running a non-busybox program as init; that didn't work, either. It seems that no matter what I do, init is run.

What could be causing this behavior?

2
  • What is the base distro that is using busybox for init? They may simply be ignoring the command line... you may want to examine the initrd and see what the scripts are actually doing. Jan 31, 2012 at 2:17
  • It's not any distro - it's my own build; which is why I'm trying to debug the init scripts. Jan 31, 2012 at 3:17

4 Answers 4

16

initrd shenanigans

If you are using initrd or initramfs, keep in mind the following:

  • rdinit= is used instead of init=

  • if rdinit= is not given, the attempted default paths are: /sbin/init, /etc/init, /bin/init and /bin/sh but not /init

    When not using initrd, /init is the first path tried, followed by the others.

v4.15 RTFS: everything is contained inside the https://github.com/torvalds/linux/blob/v4.15/init/main.c file.

First we learn that:

  • execute_comand is whatever is passed to: init=
  • ramdisk_execute_command is whatever is passed to: rdinit=

as can be seen from:

static int __init init_setup(char *str)
{
    unsigned int i;

    execute_command = str;
    /*
    * In case LILO is going to boot us with default command line,
    * it prepends "auto" before the whole cmdline which makes
    * the shell think it should execute a script with such name.
    * So we ignore all arguments entered _before_ init=... [MJ]
    */
    for (i = 1; i < MAX_INIT_ARGS; i++)
        argv_init[i] = NULL;
    return 1;
}
__setup("init=", init_setup);

static int __init rdinit_setup(char *str)
{
    unsigned int i;

    ramdisk_execute_command = str;
    /* See "auto" comment in init_setup */
    for (i = 1; i < MAX_INIT_ARGS; i++)
        argv_init[i] = NULL;
    return 1;
}
__setup("rdinit=", rdinit_setup);

where __setup is a magic way of handling up command line parameters.

start_kernel, the kernel "entry point", calls rest_init, which "calls" kernel_init on a thread:

pid = kernel_thread(kernel_init, NULL, CLONE_FS);

Then, kernel_init does:

static int __ref kernel_init(void *unused)
{
    int ret;

    kernel_init_freeable();

    [...]

    if (ramdisk_execute_command) {
        ret = run_init_process(ramdisk_execute_command);
        if (!ret)
            return 0;
        pr_err("Failed to execute %s (error %d)\n",
            ramdisk_execute_command, ret);
    }

    [...]

    if (execute_command) {
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
        panic("Requested init %s failed (error %d).",
            execute_command, ret);
    }
    if (!try_to_run_init_process("/sbin/init") ||
        !try_to_run_init_process("/etc/init") ||
        !try_to_run_init_process("/bin/init") ||
        !try_to_run_init_process("/bin/sh"))
        return 0;

    panic("No working init found.  Try passing init= option to kernel. "
        "See Linux Documentation/admin-guide/init.rst for guidance.");
}

and kernel_init_freeable does:

static noinline void __init kernel_init_freeable(void)
{

    [...]

    if (!ramdisk_execute_command)
        ramdisk_execute_command = "/init";

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
        prepare_namespace();
    }

TODO: understand sys_access.

Also note that there are further differences between ram inits and non-ram inits, e.g. console handling: Difference in execution of init with embedded vs. external initramfs?

4

On

https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt

I found:

When debugging a normal root filesystem, it's nice to be able to boot with "init=/bin/sh". The initramfs equivalent is "rdinit=/bin/sh", and it's just as useful.

So probably try ridinit=/bin/sh

3

Looking at Linux kernel source, I see that if the file /init exists, the kernel will always attempt to run it on the assumption that it's doing a ramdisk boot. Check your system to see if /init exists, if it does, then that's probably your problem.

3
  • Actually, it checks execute_command first, which comes from the kernel command line init= parameter. If it can't execute it, it prints a warning and tries to run init in various locations. This is in init/main.c in the function init_post(). I looked through the kernel printk messages and found the warning in my kernel's output, so now I have to figure out why it can't start /bin/sh or anything else that I try to start. Jan 31, 2012 at 4:00
  • The code I looked at (v3.2.2 I think) checked set ramdisk_execute_command if it was unset and then tried to run it, so you must not be that current. Too bad, because I didn't see anything else that would explain it.
    – Kyle Jones
    Jan 31, 2012 at 5:21
  • You must use rdinit when booting from ramdisk apparently: unix.stackexchange.com/a/430614/32558 Mar 16, 2018 at 12:37
0

You can custom your Linux kernel and recompile it. For 4.9 kernel, Edit the function "kernel_init" in init/main.c and try to run the following line first:

try_to_run_init_process("/bin/sh")

In addition, It might be caused by the kernel parameters passed by BootLoader.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .