Translate

Archives

GRUB2 Modules

GRUB2 has a modular architecture with support for loading and unloading code modules as needed. Modules are similar to Linux kernel modules in some respects but in other ways they are like plugins for Mozilla Firefox or the Eclipse IDE. In this post I provide an overview of what a GRUB2 module is and show you how to modify an existing module to add new functionality. I also demonstrate how to develop, integrate and test your own custom module using the GRUB2 grub-emu userspace emulator.

The current version of GRUB2 (v1.98) has 159 modules:

acpi.mod          date.mod                     gcry_sha1.mod       loopback.mod         pbkdf2.mod          terminfo.mod
affs.mod          datetime.mod                 gcry_sha256.mod     lsmmap.mod           pci.mod             test.mod
afs_be.mod        dm_nv.mod                    gcry_sha512.mod     ls.mod               play.mod            tga.mod
afs.mod           drivemap.mod                 gcry_tiger.mod      lspci.mod            png.mod             trig.mod
aout.mod          echo.mod                     gcry_twofish.mod    lvm.mod              probe.mod           true.mod
ata.mod           efiemu.mod                   gcry_whirlpool.mod  mdraid.mod           pxecmd.mod          udf.mod
ata_pthru.mod     elf.mod                      gettext.mod         memdisk.mod          pxe.mod             ufs1.mod
at_keyboard.mod   example_functional_test.mod  gfxmenu.mod         memrw.mod            raid5rec.mod        ufs2.mod
befs_be.mod       ext2.mod                     gfxterm.mod         minicmd.mod          raid6rec.mod        uhci.mod
befs.mod          extcmd.mod                   gptsync.mod         minix.mod            raid.mod            usb_keyboard.mod
biosdisk.mod      fat.mod                      gzio.mod            mmap.mod             read.mod            usb.mod
bitmap.mod        font.mod                     halt.mod            msdospart.mod        reboot.mod          usbms.mod
bitmap_scale.mod  fshelp.mod                   handler.mod         multiboot2.mod       reiserfs.mod        usbtest.mod
blocklist.mod     functional_test.mod          hashsum.mod         multiboot.mod        relocator.mod       vbeinfo.mod
boot.mod          gcry_arcfour.mod             hdparm.mod          normal.mod           scsi.mod            vbe.mod
bsd.mod           gcry_blowfish.mod            hello.mod           ntfscomp.mod         search_fs_file.mod  vbetest.mod
bufio.mod         gcry_camellia.mod            help.mod            ntfs.mod             search_fs_uuid.mod  vga.mod
cat.mod           gcry_cast5.mod               hexdump.mod         ohci.mod             search_label.mod    vga_text.mod
chain.mod         gcry_crc.mod                 hfs.mod             part_acorn.mod       search.mod          video_fb.mod
charset.mod       gcry_des.mod                 hfsplus.mod         part_amiga.mod       serial.mod          video.mod
cmp.mod           gcry_md4.mod                 iso9660.mod         part_apple.mod       setjmp.mod          videotest.mod
configfile.mod    gcry_md5.mod                 jfs.mod             part_gpt.mod         setpci.mod          xfs.mod
cpio.mod          gcry_rfc2268.mod             jpeg.mod            part_msdos.mod       sfs.mod             xnu.mod
cpuid.mod         gcry_rijndael.mod            keystatus.mod       part_sun.mod         sh.mod              xnu_uuid.mod
crc.mod           gcry_rmd160.mod              linux16.mod         parttool.mod         sleep.mod
crypto.mod        gcry_seed.mod                linux.mod           password.mod         tar.mod
datehook.mod      gcry_serpent.mod             loadenv.mod         password_pbkdf2.mod  terminal.mod


Some of the more noteworthy modules are:

  • ls, cat, echo, cmp: Provide similar functionality to their Unix command couterparts.
  • ext2, iso9660, reiserfs, xfs: Provide support for filesystems of the same name.
  • part_sun, part_gpt, part_msdos: Provide support for various partition schemes.
  • linux: Loader for Linux images
  • vga, tga, vbe, png,jpeg: Provide support for graphics and background images.

The commands to load (insmod) or unload (rmmod) a module into GRUB2 have the same names as in GNU/Linux.

When a module is loaded, or a linked-in module is initialized, the module registers one or more commands, and can also register variables, parsers, and drivers. A GRUB2 command such as hexdump requires a mapping from a name to a specific module function The file /boot/grub/command.lst contains the mappings from the command name to the module that contains the function (and code to implement that command). Here is part of the command.lst file:

cat: minicmd
chainloader: chain
clear: minicmd
cmp: cmp
.: configfile
configfile: configfile
*cpuid: cpuid
crc: crc
date: date
*drivemap: drivemap
dump: minicmd
*echo: echo
hexdump: hexdump


In many cases the command name is the same name as the module name, e.g. hexdump and hexdump.mod. In other cases a loaded module may register multiple commands, e.g. loadenv.mod registers the load_env and save_env. If a command is entered (either directly or via a script) the appropriate module(s) is loaded if the command is not already registered. A variable is a mapping from a name to a variable defined in the module. Scripts can access the variable as $name. Module variable eventing is supported; a module can get a callback when a value is assigned to a variable.

Modules are actually ELF format files. The main GRUB2 file core.img contains the necessary code to parse the ELF headers, load the module and do dynamic-linking (i.e., resolve calls to functions and variables exported by the core.img for use by modules). A module can be a built-in, i.e. part of core.img or dynamically loaded using insmod, or it can be loaded automatically when another module is loaded via insmod because the module being loaded has a dependency on it.

Dependency mappings are listed in the file moddep.lst. Here is part of this file:

video:
font: bufio video
hfsplus: fshelp
gettext: normal gzio
extcmd:
normal: crypto boot terminal charset
hashsum: extcmd normal crypto
search: extcmd search_label search_fs_uuid search_fs_file
xnu_uuid: gcry_md5
drivemap: extcmd boot mmap
password: crypto normal
read:


From the above you can see that the password module has dependencies on the crypto and normal modules. Note that the normal module is automatically loaded by core.img at startup and thus the modules crypto, boot, terminal and charset are also loaded at that time because normal has dependencies on these modules.

The source code for GRUB2 is fairly well structured and is mostly ANSI C code. Naturally there is a certain amount of assembler code (that is the nature of the beast) but the amount is small and you should never have any need to modify or even look at such code. It runs in 32-bit real mode and is single-threaded. In general there are no synchronization issues or races conditions to consider.

Turning now to the question of how to create and build a GRUB2 module. The obligatory Hello World module (./hello/hello.mod) is included with the GRUB2 source code tarball. Here is the source code for this module:

/* hello.c - test module for dynamic loading */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2003,2007  Free Software Foundation, Inc.
 *  Copyright (C) 2003  NIIBE Yutaka <gniibe@m17n.org>
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <grub/types.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/dl.h>
#include <grub/extcmd.h>
#include <grub/i18n.h>

static grub_err_t
grub_cmd_hello (struct grub_extcmd *cmd __attribute__ ((unused)),
                int argc __attribute__ ((unused)),
                char **args __attribute__ ((unused)))
{
  grub_printf ("Hello World\n");
  return 0;
}

static grub_extcmd_t cmd;

GRUB_MOD_INIT(hello)
{
  cmd = grub_register_extcmd ("hello", grub_cmd_hello, GRUB_COMMAND_FLAG_BOTH,
                              0, N_("Say \"Hello World\"."), 0);
}

GRUB_MOD_FINI(hello)
{
  grub_unregister_extcmd (cmd);
}


The above C language code should not be difficult to understand. The definitions for GRUB_MOD_INIT and GRUB_MOD_FINI are in the header dl.h. These functions are basically used to register and unregister a command, in this case hello. When you enter the command hello the function grub_cmd_hello in the dynamically loaded module hello.mod is invoked. Never try to use libc library functions in a module. All the functions you should need are available in the source tarball and have the same name as the equivalent libc function except that they are prefixed by grub_, e.g. grub_printf() and printf(), grub_strcpy() and strcpy. In addition do not include regular headers such as stdio.h.

If you are not going to pass any arguments into a command, you must specifically declare this fact in the function declaration as follows:

function_name (struct grub_extcmd *cmd __attribute__ ((unused)),
                int argc __attribute__ ((unused)),
                char **args __attribute__ ((unused)))


The necessary build directives are already present for the hello module. After compiling and installing GRUB2, you should find that the hello module was installed in /boot/grub. At the GRUB2 command prompt, you can load and execute the hello command as follows:

grub> insmod hello.mod
grub> hello
Hello World
grub>


Turning now to to the hexdump command. The source code for this command is in the ../commands subdirectory. Currently there is no option to dump the entire contents of a file in this command so I have decided to add this functionality. I see from the following code snippet that currently hexdump can take two optional arguments:

static const struct grub_arg_option options[] = {
  {"skip", 's', 0, N_("Skip offset bytes from the beginning of file."), 0, ARG_TYPE_INT},
  {"length", 'n', 0, N_("Read only LENGTH bytes."), 0, ARG_TYPE_INT},
  {0, 0, 0, 0, 0, 0}
};

static grub_err_t
grub_cmd_hexdump (grub_extcmd_t cmd, int argc, char **args)
{
  struct grub_arg_list *state = cmd->state;
  grub_ssize_t size, length;
  grub_disk_addr_t skip;
   ....
  skip = (state[0].set) ? grub_strtoull (state[0].arg, 0, 0) : 0;
  length = (state[1].set) ? grub_strtoul (state[1].arg, 0, 0) : 256;


Note that state[0] refers to the first option argument defined in options array, i.e. skip, and state[1] refers to the second option argument, i.e. length. Thus if something like -s 200 is passed as a command line argument to the hexdump command, state[0] is set to a non-zero value. I use this fact to decide when to hexdump the complete file. This I do if both state[0] and state[1} are set to 0.

Here is the source code for the new functionality:

.....
  if (!grub_strcmp (args[0], "(mem)"))
    hexdump (skip, (char *) (grub_addr_t) skip, length);
  /* new functionality */
  else if (state[0].set == 0 && state[1].set == 0)
    {
      file = grub_gzfile_open (args[0], 1);
      if (! file)
        return grub_errno;

      skip = 0;

      while ((size = grub_file_read (file, buf, 256)) > 0
             && key != GRUB_TERM_ESC)
        {
           hexdump (skip, buf, size);
           skip += size;

           while (grub_checkkey () >= 0 &&
             (key = GRUB_TERM_ASCII_CHAR (grub_getkey ())) != GRUB_TERM_ESC)
           ;
        }

      grub_putchar ('\n');
      grub_refresh ();
      grub_file_close (file);
    }
  /* end new functionality */
  else if ((args[0][0] == '(') && (args[0][namelen - 1] == ')'))
.....


The code should be pretty much self explanatory. Note that hexdump is a separate library function which formats and displays all or part of whatever is the buf buffer (depends on the numeric values in skip and size). Also, the inner while loop is activated if the pagerenvironmental variable is set. In this case, hexdump displays a —MORE— prompt and only displays the next screen of data when a key is pressed. No changes to the build system are required; you can just rebuild GRUB2, copy hexdump.mod to /boot/grub, reboot your system and test hexdump from the GRUB2 command prompt.

Next we develop a brand new module called colortest from scratch. This module will display all the color combinations supported by GRUB2 (it the same set as for GRUB Legacy) in order to check that they are correctly displayed by GRUB2.

Here is the source code for the module. Note for the sake of brevity I have left out most of the possible color combinations as these would only add several hundred additional lines of repetitive code to the listing. The source code is commented where I think an explanation is warranted.

#include <grub/env.h>
#include <grub/types.h>
#include <grub/dl.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/font.h>
#include <grub/term.h>
#include <grub/command.h>
#include <grub/extcmd.h>
#include <grub/i18n.h>

/* takes one optional argument -c */
static const struct grub_arg_option options[] =
{
     {"clear", 'c', 0, N_("Clear the screen first."), 0, 0},
     {0, 0, 0, 0, 0, 0}
};

/* since argc and argv are not used within the function they */ 
/* must be declared unused - otherwise the build fails */
static grub_err_t
grub_cmd_colortest (grub_extcmd_t cmd,
                   int argc __attribute__ ((unused)),
                   char **args __attribute__ ((unused)))
{
  struct grub_arg_list *state = cmd->state;
  grub_err_t err = GRUB_ERR_NONE;
  char color_normal[50];
  const char *tmp;

  /* save the current color string for color_normal. It is in the environment  */
  /* set default if none in the environment otherwise error messages in grub-emu */.
  tmp = grub_env_get("color_normal");
  if (*tmp)
      grub_strcpy(color_normal, tmp);
  else
      grub_strcpy(color_normal, "light-gray/black");

  /* clear the screen if -c passed on command line */
  if (state[0].set)
    grub_cls();

  /* set the current color state to normal jsut in case it it currently highlight */
  grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);

  /* black */
  /* change the color_normal setting in the environment */
  grub_env_set ("color_normal", "dark-gray/black");
  /* print this string in dark-gray on black.  Black as background is transparent */
  grub_printf (" dark-gray/black          ");
  grub_env_set ("color_normal", "light-blue/black");
  grub_printf (" light-blue/black         ");
  grub_env_set ("color_normal", "light-green/black");
  grub_printf (" light-green/black        ");
  grub_env_set ("color_normal", "light-cyan/black");
  grub_printf (" light-cyan/black         \n");
  grub_env_set ("color_normal", "light-red/black");
  grub_printf (" light-red/black          ");
  grub_env_set ("color_normal", "light-magenta/black");
  grub_printf (" light-magenta/black      ");
  grub_env_set ("color_normal", "yellow/black");
  grub_printf (" yellow/black             ");
  grub_env_set ("color_normal", "white/black");
  grub_printf (" white/black              \n");

  /* blue */
  grub_env_set ("color_normal", "dark-gray/blue");
  grub_printf (" dark-gray/blue           ");
  grub_env_set ("color_normal", "light-blue/blue");
  grub_printf (" light-blue/blue          ");
  grub_env_set ("color_normal", "light-green/blue");
  grub_printf (" light-green/blue         ");
  grub_env_set ("color_normal", "light-cyan/blue");
  grub_printf (" light-cyan/blue          \n");

  grub_env_set ("color_normal", "light-red/blue");
  grub_printf (" light-red/blue           ");
  grub_env_set ("color_normal", "light-magenta/blue");
  grub_printf (" light-magenta/blue       ");
  grub_env_set ("color_normal", "yellow/blue");
  grub_printf (" yellow/blue              ");
  grub_env_set ("color_normal", "white/blue");
  grub_printf (" white/blue               \n");

  /* revert to the original color_normal */
  grub_env_set ("color_normal", color_normal); 
  grub_printf ("\n\n");

  /* black highlight */
  /* change the color_highlight setting in the environment */
  grub_env_set ("color_highlight", "black/blue");
  /* enable color_highlight and print a string */
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" black/blue               ");
  grub_env_set ("color_highlight", "black/green");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" black/green              ");
  grub_env_set ("color_highlight", "black/cyan");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" black/cyan               ");
  grub_env_set ("color_highlight", "black/red");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" black/red                \n");

  grub_env_set ("color_highlight", "black/magenta");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" black/magenta            ");
  grub_env_set ("color_highlight", "black/brown");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" black/brown              ");
  grub_env_set ("color_highlight", "black/light-gray");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" black/light-gray         \n");

  /* blue highlight */
  grub_env_set ("color_highlight", "blue/green");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" blue/green               ");
  grub_env_set ("color_highlight", "blue/cyan");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" blue/cyan                ");
  grub_env_set ("color_highlight", "blue/red");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" blue/red                 \n");

  grub_env_set ("color_highlight", "blue/magenta");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" blue/magenta             ");
  grub_env_set ("color_highlight", "blue/brown");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" blue/brown               ");
  grub_env_set ("color_highlight", "blue/light-gray");
  grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT);
  grub_printf (" blue/light-gray          \n");

  /* revert to original color_normal */
  grub_env_set ("color_normal", color_normal);
  grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
  grub_printf ("\n");

  /* and exit */
  return err;
}

static grub_extcmd_t cmd;

GRUB_MOD_INIT(colortest)
{
  cmd = grub_register_extcmd ("colortest", grub_cmd_colortest,
                               GRUB_COMMAND_FLAG_BOTH,
                               "[-c]",
                               /* the _N construct is for message localization */
                               N_("Test supported color combinations."),
                               options);
}

GRUB_MOD_FINI(colortest)
{
    grub_unregister_extcmd (cmd);
}


Now that the C code is written, how can we integrate colortest into the existing GRUB2 build system so that colortest.mod is automatically built when we enter make. at the GRUB2 build top level directory? It turns out to be long-winded but easy. Just add the following lines to ../conf/common.mk:

# For colortest.mod.
pkglib_MODULES += colortest.mod
colortest_mod_SOURCES = commands/colortest.c

clean-module-colortest.mod.1:
        rm -f colortest.mod mod-colortest.o mod-colortest.c pre-colortest.o colortest_mod-commands_colortest.o und-colortest.lst

CLEAN_MODULE_TARGETS += clean-module-colortest.mod.1

clean-module-colortest.mod-symbol.1:
        rm -f def-colortest.lst

CLEAN_MODULE_TARGETS += clean-module-colortest.mod-symbol.1
DEFSYMFILES += def-colortest.lst
mostlyclean-module-colortest.mod.1:
        rm -f colortest_mod-commands_colortest.d

MOSTLYCLEAN_MODULE_TARGETS += mostlyclean-module-colortest.mod.1
UNDSYMFILES += und-colortest.lst
ifneq ($(TARGET_APPLE_CC),1)
colortest.mod: pre-colortest.o mod-colortest.o $(TARGET_OBJ2ELF)
        -rm -f $@
        $(TARGET_CC) $(colortest_mod_LDFLAGS) $(TARGET_LDFLAGS) -Wl,-r,-d -o $@ pre-colortest.o mod-colortest.o
        if test ! -z "$(TARGET_OBJ2ELF)"; then ./$(TARGET_OBJ2ELF) $@ || (rm -f $@; exit 1); fi
        $(STRIP) --strip-unneeded -K grub_mod_init -K grub_mod_fini -K _grub_mod_init -K _grub_mod_fini -R .note -R .comment $@
else
colortest.mod: pre-colortest.o mod-colortest.o $(TARGET_OBJ2ELF)
        -rm -f $@
        -rm -f $@.bin
        $(TARGET_CC) $(colortest_mod_LDFLAGS) $(TARGET_LDFLAGS) -Wl,-r,-d -o $@.bin pre-colortest.o mod-colortest.o
        $(OBJCONV) -f$(TARGET_MODULE_FORMAT) -nr:_grub_mod_init:grub_mod_init -nr:_grub_mod_fini:grub_mod_fini -wd1106 -nu -nd $@.bin $@
        -rm -f $@.bin
endif

pre-colortest.o: $(colortest_mod_DEPENDENCIES) colortest_mod-commands_colortest.o
        -rm -f $@
        $(TARGET_CC) $(colortest_mod_LDFLAGS) $(TARGET_LDFLAGS) -Wl,-r,-d -o $@ colortest_mod-commands_colortest.o

mod-colortest.o: mod-colortest.c
        $(TARGET_CC) $(TARGET_CPPFLAGS) $(TARGET_CFLAGS) $(colortest_mod_CFLAGS) -c -o $@ $<

mod-colortest.c: $(builddir)/moddep.lst $(srcdir)/genmodsrc.sh
        sh $(srcdir)/genmodsrc.sh 'colortest' $< > $@ || (rm -f $@; exit 1)
ifneq ($(TARGET_APPLE_CC),1)
def-colortest.lst: pre-colortest.o
        $(NM) -g --defined-only -P -p $< | sed 's/^\([^ ]*\).*/\1 colortest/' > $@
else
def-colortest.lst: pre-colortest.o
        $(NM) -g -P -p $< | grep -E '^[a-zA-Z0-9_]* [TDS]'  | sed 's/^\([^ ]*\).*/\1 colortest/' > $@
endif

und-colortest.lst: pre-colortest.o
        echo 'colortest' > $@
        $(NM) -u -P -p $< | cut -f1 -d' ' >> $@

colortest_mod-commands_colortest.o: commands/colortest.c $(commands/colortest.c_DEPENDENCIES)
        $(TARGET_CC) -Icommands -I$(srcdir)/commands $(TARGET_CPPFLAGS)  $(TARGET_CFLAGS) $(colortest_mod_CFLAGS) -MD -c -o $@ $<
-include colortest_mod-commands_colortest.d

clean-module-colortest_mod-commands_colortest-extra.1:
        rm -f cmd-colortest_mod-commands_colortest.lst fs-colortest_mod-commands_colortest.lst partmap-colortest_mod-commands_colortest.lst handler-colortest_mod-commands_colortest.lst parttool-colortest_mod-commands_colortest.lst video-colortest_mod-commands_colortest.lst terminal-colortest_mod-commands_colortest.lst

CLEAN_MODULE_TARGETS += clean-module-colortest_mod-commands_colortest-extra.1

COMMANDFILES += cmd-colortest_mod-commands_colortest.lst
FSFILES += fs-colortest_mod-commands_colortest.lst
PARTTOOLFILES += parttool-colortest_mod-commands_colortest.lst
PARTMAPFILES += partmap-colortest_mod-commands_colortest.lst
HANDLERFILES += handler-colortest_mod-commands_colortest.lst
TERMINALFILES += terminal-colortest_mod-commands_colortest.lst
VIDEOFILES += video-colortest_mod-commands_colortest.lst
cmd-colortest_mod-commands_colortest.lst: commands/colortest.c $(commands/colortest.c_DEPENDENCIES) gencmdlist.sh
        set -e;           $(TARGET_CC) -Icommands -I$(srcdir)/commands $(TARGET_CPPFLAGS)  $(TARGET_CFLAGS) $(colortest_mod_CFLAGS) -E $<         | sh $(srcdir)/gencmdlist.sh colortest > $@ || (rm -f $@; exit 1)

fs-colortest_mod-commands_colortest.lst: commands/colortest.c $(commands/colortest.c_DEPENDENCIES) genfslist.sh
        set -e;           $(TARGET_CC) -Icommands -I$(srcdir)/commands $(TARGET_CPPFLAGS)  $(TARGET_CFLAGS) $(colortest_mod_CFLAGS) -E $<         | sh $(srcdir)/genfslist.sh colortest > $@ || (rm -f $@; exit 1)

parttool-colortest_mod-commands_colortest.lst: commands/colortest.c $(commands/colortest.c_DEPENDENCIES) genparttoollist.sh
        set -e;           $(TARGET_CC) -Icommands -I$(srcdir)/commands $(TARGET_CPPFLAGS)  $(TARGET_CFLAGS) $(colortest_mod_CFLAGS) -E $<         | sh $(srcdir)/genparttoollist.sh colortest > $@ || (rm -f $@; exit 1)

partmap-colortest_mod-commands_colortest.lst: commands/colortest.c $(commands/colortest.c_DEPENDENCIES) genpartmaplist.sh
        set -e;           $(TARGET_CC) -Icommands -I$(srcdir)/commands $(TARGET_CPPFLAGS)  $(TARGET_CFLAGS) $(colortest_mod_CFLAGS) -E $<         | sh $(srcdir)/genpartmaplist.sh colortest > $@ || (rm -f $@; exit 1)

handler-colortest_mod-commands_colortest.lst: commands/colortest.c $(commands/colortest.c_DEPENDENCIES) genhandlerlist.sh
        set -e;           $(TARGET_CC) -Icommands -I$(srcdir)/commands $(TARGET_CPPFLAGS)  $(TARGET_CFLAGS) $(colortest_mod_CFLAGS) -E $<         | sh $(srcdir)/genhandlerlist.sh colortest > $@ || (rm -f $@; exit 1)

terminal-colortest_mod-commands_colortest.lst: commands/colortest.c $(commands/colortest.c_DEPENDENCIES) genterminallist.sh
        set -e;           $(TARGET_CC) -Icommands -I$(srcdir)/commands $(TARGET_CPPFLAGS)  $(TARGET_CFLAGS) $(colortest_mod_CFLAGS) -E $<         | sh $(srcdir)/genterminallist.sh colortest > $@ || (rm -f $@; exit 1)

video-colortest_mod-commands_colortest.lst: commands/colortest.c $(commands/colortest.c_DEPENDENCIES) genvideolist.sh
        set -e;           $(TARGET_CC) -Icommands -I$(srcdir)/commands $(TARGET_CPPFLAGS)  $(TARGET_CFLAGS) $(colortest_mod_CFLAGS) -E $<         | sh $(srcdir)/genvideolist.sh colortest > $@ || (rm -f $@; exit 1)

colortest_mod_CFLAGS = $(COMMON_CFLAGS)
colortest_mod_LDFLAGS = $(COMMON_LDFLAGS)


Most of the above is boilerplate. I simply copied it from an existing target such as tga in common.mk and changed the target name to colortest. If you now execute make from the GRUB2 build toplevel directory, it should contain the module colortest.mod after the build completes if the build was successful.

Here is a detailed listing of the commands used to build colortest.mod:

gcc -Icommands -I./commands -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/4.4.4/include  \
       -I./include -I. -I./include -Wall -W  -Os  -DGRUB_MACHINE_PCBIOS=1 -Wall -W -Wshadow  \
      -Wpointer-arith -Wmissing-prototypes  -Wundef -Wstrict-prototypes -g -falign-jumps=1 \ 
      -falign-loops=1 -falign-functions=1 -mno-mmx -mno-sse -mno-sse2 -mno-3dnow \ 
      -fno-dwarf2-cfi-asm -m32 -fno-stack-protector -mno-stack-arg-probe -Werror -fno-builtin -mrtd \ 
      -mregparm=3 -m32 -MD -c -o colortest_mod-commands_colortest.o commands/colortest.c
rm -f pre-colortest.o
gcc -m32 -nostdlib -m32 -Wl,--build-id=none -Wl,-r,-d -o pre-colortest.o colortest_mod-commands_colortest.o
nm -g --defined-only -P -p pre-colortest.o | sed 's/^\([^ ]*\).*/\1 colortest/' > def-colortest.lst
echo 'colortest' > und-colortest.lst
nm -u -P -p pre-colortest.o | cut -f1 -d' ' >> und-colortest.lst


As you can see below, a 32-bit ELF relocatable binary is produced.

file colortest.mod
colortest.mod: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
# 


There are a number of ways to test and verify the new colortest module. You can copy colortest.mod into /boot/grub, modify /boot/grub/command.lst to add the following entry:

colortest: colortest


and reboot your system to the GRUB2 command prompt. From there you can load the colortest module using insmod and invoke the grub_colortest function by entering the command colortest or colortest -c at the grub> prompt. A drawback to this method is that it requires you to reboot your system every time you make a change to the colortest source code or build environment.

If you want to avoid having to repeatedly reboot your system you can test the functionality of colortest using the GRUB2 userspace emulator grub-emu. Note that grub-emu is not used for system booting; it is a standalone utility which you can run from the command line after you boot your system into GNU/Linux. It emulates, as best it can but with some limitations, the behavior of the GRUB2 command line.

To build grub-emu you need to have both the ncurses-libs and ncurses-devel packages installed. I did not find much documentation on how to build grub-emu but discovered how to do so mostly by trial and error. In the end it turned out to be quite simple.

./make clean
./configure --platform=emu
./make
./grub-emu


This should get you to the grub-emu prompt after a few warning messages are displayed. These warning messages are normal. Here is what grub-emu outputs when it is invoked with the -h option:

# ./grub-emu -h
Usage: ./grub-emu [OPTION]...

GRUB emulator.

  -r, --root-device=DEV     use DEV as the root device [default=guessed]
  -m, --device-map=FILE     use FILE as the device map [default=/boot/grub/device.map]
  -d, --directory=DIR       use GRUB files in the directory DIR [default=/boot/grub]
  -v, --verbose             print verbose messages
  -H, --hold[=SECONDS]      wait until a debugger will attach
  -h, --help                display this message and exit
  -V, --version             print version information and exit

Report bugs to .
#


By the way, enter exit or reboot to exit grub-emu. Notice that no modules such as normal.mod or colortest.mod are built when building the GRUB2 emulator. That is because insmod does not work in grub-emu and therefore building the modules is unnecessary. The insmod command is there but it cannot do anything. If you try to use insmod to load a module from /boot/grub, it will respond with an invalid arch independent ELF magic error message. Modules are either built-in during the build process or are not available. At this time, if you try to execute colortest from within grub-emu you will get a error: unknown command ‘colortest’ error message.

To get grub-emu to recognize the colortest command a number of files need to be modified and grub-emu rebuilt. First, add the following two lines to grub-emu-init.c:

  grub_colortest_init ();
  grub_colortest_fini ();


The first line should be added to the grub_init_all procedure and the second line to the grub_fini_all procedure. Two suitable function prototypes, modeled on the other existing function prototypes, should be added to grub-emu-init.h also. The purpose of grub_init_all and grub_fini_all (see …/util/grub-emu.c) is to load the specified modules at grub-emu startup and unload them when grub-emu is terminating.

Next you need to modify ../conf/any-emu.mk to add support for linking colortest into grub-emu when building grun-emu. At a minimum add commands/colortest.c to the grub_emu_SOURCES list. While not absolutely required, I would add grub_emu-commands_colortest.o and grub_emu-commands_colortest.d to the list of files to be removed by the cleanup directives, e.g. clean-utility-grub-emu.1:, etc. Finally, we need to tell any-emu.mk how to compile colortest.o for inclusion in grub-emu by adding the following directives to the file:

grub_emu-commands_colortest.o: commands/colortest.c $(commands/colortest.c_DEPENDENCIES)
        $(CC) -Icommands -I$(srcdir)/commands $(CPPFLAGS) $(CFLAGS) -DGRUB_UTIL=1 $(grub_emu_CFLAGS) -MD -c -o $@ $<
-include grub_emu-commands_colortest.d


After rebuilding grub-emu, the colortest command should be listed when you enter the help command.

Here is a screenshot of grub-emu displaying the results of the colortest command.
grub2 emulator image 1
Another way to safely test a new module is to host GRUB2 on an IA32 processor emulator such as Qemu or Bochs. However, I am not going to cover those methods in this post. If I find the time I will cover them in a separate post in the near future.

I am going to stop writing now. Hopefully, after reading this post, you have a better understanding of how to write, integrate and test a GRUB2 module. Please let me know if there is other pertinent information I should add to this post which would assist readers in understanding the relevant issues.
 

P.S. I built and tested the examples included in this post on X64 platform running Fedora 13.

Comments are closed.