You are on page 1of 6

Problems With UEFI Shell Options

Problems With UEFI Shell Options


Finnbarr P. Murphy
(fpm@fpmurphy.com) Recently I needed to work down at the UEFI Shell level. The platform I was on was a BioStar TZ77XE4 which, by the way, was recently certified for Microsoft Windows 8 Client Family, x64. No UEFI Shell was installed, so I UEFI-installed Fedora 17 x64 and copied the pre-compiled UEFI Shell from the UDK 2010 to /boot/efi, i.e. the mount point of the ESP filesystem. Note that while BioStar say that a UEFI Shell should be named ShellX64.efi, I found that Shell.efi also worked and that is what I used.

OPTION file-name

file-name-options The command-line options that are passed to file-name when it is invoked.

Options (from the table below) which control the initialization behaviour of the ShellOpt-options shell. These options are read from the EFI global variable ShellOpt and are processed before options or file-name. OPTION -nostartup -noconsoleout -noconsolein RESULT

Fo r

-delay [n]

-nointerrupt -nomap -noversion

While the command line options worked as expected, I wished to use the ShellOpt global variable to save repeatedly typing in various command line options. This turned out not to work as expected. The set command did not work because it cannot accept variable values that start with a -. So, frustrated by what should have been a simple task, I ended up digging through the EDK2
07-13-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 1/6

pe rs o

options

Options (from the table below) which control the initialization behavior of the shell.

The default startup script startup.nsh will not be executed. Console output from the shell applications will not be displayed. This has no effect for UEFI Shells that do not support an interactive mode. Console input will not be accepted from the user. This has no effect for UEFI Shells that do not support an interactive mode. Specifies the integer number of seconds the shell will delay prior to the execution of startup.nsh. Ignored for shell level 0 or if nostartup is specified. If n is not specified, the default is 5 seconds. If 0 is specified, then there will be no delay. If nointerrupt is specified, then there will be no delay. Execution interruption is not allowed. This has no effect for UEFI Shells that do not support an interactive mode. Default mappings will not be displayed. UEFI Shell version information will not be displayed.

nn a

The name of a UEFI shell application or script to be executed after initialization is complete. By default, if file-name is specified, then -nostartup is implied. Scripts are not supported by level 0 support.

lu

The command-line options are separated by the space or tab character. Options are processed left-to-right. The following two tables describe the standard command-line options. DESCRIPTION

se

shell.efi [ShellOpt-options] [options] [file-name [file-name-options]]

on

The UEFI Shell itself is just another UEFI application. It takes command-line options that are null-terminated UCS-2 (2-byte Universal Character Set) encoded strings. The syntax is:

ly

Problems With UEFI Shell Options

codebase to see when was going on. The relevant code is in /ShellPkg/Application/Shell/ShellParametersProtocol.c. What I found is that the code relating to ShellOpt could never have worked for two reasons:
q

Incorrect use of &FullCommandLine instead of FullCommandLine as one of the arguments to the GetVariable Runtime Service defined in ShellEnvVar.h The contents of the FullCommandLine buffer are replaced with the LoadOptions string instead of pre-pending the LoadOptions string to whatever is already in the FullCommandLine buffer.

Here are the diffs to ShellParametersProtocol.c which contain the fixes for the two problems described above:

Fo r

Even with my fixes in a newly built UEFI Shell, I was still faced with the problem of how to create the ShellOpt global variable. Rather than dig into the set command and modify it so that it accepted arguments in the form of -arg (moderately difficult probably requiring a POSIX-like option), I decided to roll my own application to set and unset the ShellOpt global variable. This uncovered another interesting issue. The UEFI Shell Specification describes ShellOpt as a global variable. However, the source code does not use EFI_GLOBAL_VARIABLE; instead it uses EFI_SHELL_INTERFACE_GUID. I suspect this is another coding error but I choose not to change that particular code in case I broke code elsewhere. Instead, I choose to use EFI_SHELL_INTERFACE_GUID in my application. Here is the simple EFI application that I wrote to enable me to set and unset ShellOpt:

// // Copyright (c) 2012 Finnbarr P. Murphy. All rights reserved. // #include <efi.h> #include <efilib.h> #define EFI_SHELL_INTERFACE_GUID \ (EFI_GUID) {0x47c7b223, 0xc42a, 0x11d2, {0x8e,0x57,0x00,0xa0,0xc9,0x69,0x72,0x3b}}

07-13-2012

pe rs o

> > Status = SHELL_GET_ENVIRONMENT_VARIABLE(L"ShellOpt", &Size, &FullCommandLine); > if (Status == EFI_BUFFER_TOO_SMALL) { > FullCommandLine = AllocateZeroPool(Size + LoadedImage->LoadOptionsSize); > Status = SHELL_GET_ENVIRONMENT_VARIABLE(L"ShellOpt", &Size, &FullCommandLine); 300c299,304 if (Status == EFI_NOT_FOUND) { > // > // no parameters via environment... ok > // > } else { > if (EFI_ERROR(Status)) { 301a306 > } 303a309 > 312d317 LoadOptionsSize != 0) { LoadOptions; = f) if (LoadedImage->LoadOptionsSize != 0){ > StrCpy(FullCommandLine, LoadedImage->LoadOptions);

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

$ diff ShellParametersProtocol.c ShellParametersProtocol.c.org 40d239

ly
2/6

Problems With UEFI Shell Options // There is some confusion here - the EDK2 shell source code uses SHELL_VARIABLE_GUID wher eas // the Shell 2.0 specification mandates EFI_GLOBAL_VARIABLE. See Section 3.2, Table 2. #define EFI_GLOBAL_VARIABLE \ (EFI_GUID) {0x8BE4DF61, 0x93CA, 0x11d2, {0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C }} #define SHELL_VARIABLE_GUID \ (EFI_GUID) {0x158def5a, 0xf656, 0x419c, {0xb0,0x27,0x7a,0x31,0x92,0xc0,0x79,0xd2}} #define BUFSIZE 2048 typedef enum { ARG_NO_ATTRIB = 0x0, ARG_IS_QUOTED = 0x1, ARG_PARTIALLY_QUOTED = 0x2, ARG_FIRST_HALF_QUOTED = 0x4, ARG_FIRST_CHAR_IS_ESC = 0x8 } EFI_SHELL_ARG_INFO_TYPES; struct _EFI_SHELL_ARG_INFO { UINT32 Attributes; } __attribute__((packed)) __attribute__((aligned (1))); typedef struct _EFI_SHELL_ARG_INFO EFI_SHELL_ARG_INFO; G struct _EFI_SHELL_INTERFACE { EFI_HANDLE ImageHandle; EFI_LOADED_IMAGE *Info; CHAR16 **Argv; UINTN Argc; CHAR16 **RedirArgv; UINTN RedirArgc; EFI_FILE *StdIn; EFI_FILE *StdOut; EFI_FILE *StdErr; EFI_SHELL_ARG_INFO *ArgInfo; BOOLEAN EchoOn; } __attribute__((packed)) __attribute__((aligned (1))); typedef struct _EFI_SHELL_INTERFACE EFI_SHELL_INTERFACE; static struct { CHAR16 *name; UINTN len; } shellopts[] = { { L"-nomap", 6 }, { L"-nostartup", 10 }, { L"-startup", 8 }, { L"-noversion", 10 }, { L"-noconsolein", 12 }, { L"-noconsoleout", 13 }, { L"-nointerrupt", 12 }, { L"-delay", 6 }, { NULL, 0 } }; static EFI_STATUS get_args(EFI_HANDLE image, UINTN *argc, CHAR16 ***argv) { EFI_STATUS status; EFI_SHELL_INTERFACE *shell; EFI_GUID gEfiShellInterfaceGuid = EFI_SHELL_INTERFACE_GUID; status = uefi_call_wrapper(BS->OpenProtocol, 6, image, &amp;gEfiShellInterfaceGuid, (VOID **)&amp;shell, image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(status)) return status; *argc = shell->Argc; *argv = shell->Argv; status = uefi_call_wrapper(BS->CloseProtocol, 4, image, &amp;gEfiShellInterfaceGuid, image, NULL); return status; } static BOOLEAN

Fo r

07-13-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

3/6

Problems With UEFI Shell Options is_number(CHAR16* str) { CHAR16 *s = str; while (*s) { if (*s < L'0' || *s > L'9') return FALSE; s++; } return TRUE; } static void usage(void) { Print(L"Usage: shellopt -s|--set [-nomap] [-nostartup | -startup] [-noversion] \\\n"); Print(L" [-noconsolein] [-noconsoleout] [-nointerrupt] \\\n"); Print(L" [-delay[:n]]\n"); Print(L" shellopt -c|--clear\n"); } EFI_STATUS efi_main (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) { EFI_STATUS status; EFI_GUID gEfiGlobalVariableGuid = SHELL_VARIABLE_GUID; CHAR16 buffer[BUFSIZE]; CHAR16 *bufptr = buffer; UINTN bufsize = 0; UINTN argc; CHAR16 **argv; int i, j, Match; InitializeLib(image, systab); status = get_args(image, &amp;argc, &amp;argv); if (EFI_ERROR(status)) { Print(L"ERROR: Parsing command line arguments: %d\n", status); return status; } if (argc < 2) { usage(); return EFI_SUCCESS; } else if (argc == 2) { if (!StrCmp(argv[1], L"--help") || !StrCmp(argv[1], L"/help") || !StrCmp(argv[1], L"-h") || !StrCmp(argv[1], L"-?")) { usage(); return EFI_SUCCESS; } if (!StrCmp(argv[1], L"-c") || !StrCmp(argv[1], L"--clear")) { bufsize = 0; bufptr = (CHAR16 *)NULL; } } else if (argc > 2 &amp;&amp; (!StrCmp(argv[1], L"-s") || !StrCmp(argv[1], L"--set")) ) { } else if (argc > 2 &amp;&amp; (!StrCmp(argv[1], L"-s") || !StrCmp(argv[1], L"--set")) ) { ZeroMem(bufptr, BUFSIZE); for (i = 2; i < argc; i++) { StrLwr(argv[i]); Match = 0; for (j = 0; shellopts[j].name != NULL; j++) { if (!StrCmp(shellopts[j].name, argv[i])) { StrCat(buffer, argv[i]); StrCat(buffer, L" "); Match = 1; // handle delay number if one entered on command line if (!StrCmp(L"-delay", argv[i]) &amp;&amp; i < argc &amp;&amp; is_numb er(argv[i+1])) { i++;

Fo r

07-13-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

4/6

Problems With UEFI Shell Options StrCat(buffer, argv[i]); StrCat(buffer, L" "); } break; } } if (!Match) { Print(L"ERROR: Invalid shell startup option: %s\n", argv[i]); return EFI_INVALID_PARAMETER; } } // hack - but it works! bufsize = StrLen(buffer) * 2; } else { usage(); return EFI_INVALID_PARAMETER; } status = uefi_call_wrapper(RT->SetVariable, 5, L"ShellOpt", &amp;gEfiGlobalVariableGu EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCES S, bufsize, bufptr); if (status != EFI_SUCCESS &amp;&amp; status != EFI_NOT_FOUND) Print(L"ERROR: SetVariable: %d\n", status); return status; }

Assuming you have Peter Jones gnu-efi package installed, here is a suitable Makefile for Fedora 17 which will correctly build the shellopt application.

SRCDIR = . PREFIX := /usr HOSTARCH = $(shell uname -m | sed s,i[3456789]86,ia32,) ARCH := $(shell uname -m | sed s,i[3456789]86,ia32,) INCDIR = -I. CPPFLAGS = -DCONFIG_$(ARCH) CFLAGS = $(ARCH3264) -g -O0 -fpic -Wall -fshort-wchar -fno-strict-aliasing -fno-mergeconstants --std=gnu99 -D_GNU_SOURCE ASFLAGS = $(ARCH3264) LDFLAGS = -nostdlib INSTALL = install CC = gcc AS = as LD = ld.bfd AR = ar RANLIB = ranlib OBJCOPY = objcopy ifeq ($(ARCH), ia32) LIBDIR := $(PREFIX)/lib ifeq ($(HOSTARCH), x86_64) ARCH3264 := -m32 endif endif ifeq ($(ARCH), x86_64) CFLAGS += -mno-red-zone LIBDIR := $(PREFIX)/lib64 ifeq ($(HOSTARCH), ia32) ARCH3264 := -m64 endif endif FORMAT=efi-app-$(HOSTARCH) LDFLAGS = -nostdlib -T $(LIBDIR)/gnuefi/elf_$(HOSTARCH)_efi.lds -shared -Bsymbolic $(LIBDI R)/gnuefi/crt0-efi-$(HOSTARCH).o -L$(LIBDIR) LIBS=-lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name) CCLDFLAGS =

Fo r

07-13-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se
5/6

on

id,

ly

Problems With UEFI Shell Options CFLAGS = -I/usr/include/efi/ -I/usr/include/efi/$(HOSTARCH)/ -I/usr/include/efi/protocol fpic -fshort-wchar -fno-reorder-functions -fno-strict-aliasing -fno-merge-constants -mno-r ed-zone -Wimplicit-function-declaration TARGETS = shellopt.efi all : $(TARGETS) clean : @rm -rf *.o *.a *.so $(TARGETS) .PHONY: all clean install %.efi : %.so $(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic -j .dynsym -j .rel \ -j .rela -j .reloc --target=$(FORMAT) $*.so $@ %.so: %.o $(LD) $(LDFLAGS) -o $@ $^ $(LIBS) %.o: %.c $(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -c $< -o $@ %.S: %.c $(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -S $< -o $@ %.E: %.c $(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -E $< -o $@

You can download the above two files from Github. You can also watch a YouTube video on how to use the shellopt application to set and unset the ShellOpt variable. A number of bloggers have recently stated that the UEFI codebase is larger than the current Linux kernel codebase (which is over 15 million LOC.) This is erroneous as CLOC reports just over 2 million LOC in the current EDK2 trunk. Even so, the UEFI codebase is very large and not that many eyeballs have examined the source code in detail. I spotted numerous other lines of iffy code while hunting down and resolving the ShellOpt issue. I suspect that there numerous latent bugs in the codebase which will only come to light as UEFI becomes mainstream.

Fo r

07-13-2012

pe rs o

Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

6/6

You might also like