6
Problems With UEFI Shell Options Problems With UEFI Shell Options Finnbarr P. Murphy ([email protected]) 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. 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: shell.efi [ShellOpt-options] [options] [file-name [file-name-options]] 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. OPTION DESCRIPTION file-name 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. file-name-options The command-line options that are passed to file-name when it is invoked. options Options (from the table below) which control the initialization behavior of the shell. ShellOpt-options Options (from the table below) which control the initialization behaviour of the shell. These options are read from the EFI global variable ShellOpt and are processed before options or file-name. OPTION RESULT -nostartup The default startup script startup.nsh will not be executed. -noconsoleout Console output from the shell applications will not be displayed. This has no effect for UEFI Shells that do not support an interactive mode. -noconsolein Console input will not be accepted from the user. This has no effect for UEFI Shells that do not support an interactive mode. -delay [n] 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. -nointerrupt Execution interruption is not allowed. This has no effect for UEFI Shells that do not support an interactive mode. -nomap Default mappings will not be displayed. -noversion UEFI Shell version information will not be displayed. 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 For personnal use only 07-13-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 1/6

Problems With UEFI Shell Options

Embed Size (px)

DESCRIPTION

The (U)EFI Shell has been around one form or another for nearly 15 years. Recenly I looked at Shell startup options and found that the relevant code in the Shell was badly broken and could never have worked. This post details my findings and solutions.Read more: http://blog.fpmurphy.com/2012/07#ixzz20Sj2nmL6

Citation preview

Page 1: Problems With UEFI Shell Options

Problems With UEFI Shell Options

Problems With UEFI Shell OptionsFinnbarr P. Murphy

([email protected])

Recently I needed to work down at the UEFI Shell level. The platform I was on was a BioStarTZ77XE4 which, by the way, was recently certified for Microsoft Windows 8 Client Family, x64. NoUEFI Shell was installed, so I UEFI-installed Fedora 17 x64 and copied the pre-compiled UEFIShell from the UDK 2010 to /boot/efi, i.e. the mount point of the ESP filesystem. Note that whileBioStar say that a UEFI Shell should be named ShellX64.efi, I found that Shell.efi also worked andthat is what I used.

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

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

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

OPTION DESCRIPTION

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

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

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

ShellOpt-options Options (from the table below) which control the initialization behaviour of theshell. These options are read from the EFI global variable ShellOpt and areprocessed before options or file-name.

OPTION RESULT-nostartup The default startup script startup.nsh will not be executed.

-noconsoleout Console output from the shell applications will not be displayed. This has no effectfor UEFI Shells that do not support an interactive mode.

-noconsolein Console input will not be accepted from the user. This has no effect for UEFIShells that do not support an interactive mode.

-delay [n] Specifies the integer number of seconds the shell will delay prior to the executionof startup.nsh. Ignored for shell level 0 or if –nostartup is specified. If n is notspecified, 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.

-nointerrupt Execution interruption is not allowed. This has no effect for UEFI Shells that donot support an interactive mode.

-nomap Default mappings will not be displayed.-noversion UEFI Shell version information will not be displayed.

While the command line options worked as expected, I wished to use the ShellOpt global variableto save repeatedly typing in various command line options. This turned out not to work asexpected. 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

For p

erson

nal u

se on

ly

07-13-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 1/6

Page 2: Problems With UEFI Shell Options

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 isthat the code relating to ShellOpt could never have worked for two reasons:

Incorrect use of &FullCommandLine instead of FullCommandLine as one of the arguments to the●

GetVariable Runtime Service defined in ShellEnvVar.hThe 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 problemsdescribed above:

$ diff ShellParametersProtocol.c ShellParametersProtocol.c.org40d239

> > 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> 312d317LoadOptionsSize != 0) {LoadOptions;= f) if (LoadedImage->LoadOptionsSize != 0){> StrCpy(FullCommandLine, LoadedImage->LoadOptions);

Even with my fixes in a newly built UEFI Shell, I was still faced with the problem of how to createthe ShellOpt global variable. Rather than dig into the set command and modify it so that itaccepted 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 aglobal variable. However, the source code does not use EFI_GLOBAL_VARIABLE; instead it usesEFI_SHELL_INTERFACE_GUID. I suspect this is another coding error but I choose not to changethat particular code in case I broke code elsewhere. Instead, I choose to useEFI_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}}

For p

erson

nal u

se on

ly

07-13-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 2/6

Page 3: Problems With UEFI Shell Options

Problems With UEFI Shell Options

// There is some confusion here - the EDK2 shell source code uses SHELL_VARIABLE_GUID whereas// 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 2048typedef 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;Gstruct _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_STATUSget_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

For p

erson

nal u

se on

ly

07-13-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 3/6

Page 4: Problems With UEFI Shell Options

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 voidusage(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_STATUSefi_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_number(argv[i+1])) { i++;

For p

erson

nal u

se on

ly

07-13-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 4/6

Page 5: Problems With UEFI Shell Options

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;gEfiGlobalVariableGuid, EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS, 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 Jone‘s gnu-efi package installed, here is a suitable Makefile for Fedora17 which will correctly build the shellopt application.

SRCDIR = .PREFIX := /usrHOSTARCH = $(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-merge-constants --std=gnu99 -D_GNU_SOURCEASFLAGS = $(ARCH3264)LDFLAGS = -nostdlibINSTALL = installCC = gccAS = asLD = ld.bfdAR = arRANLIB = ranlibOBJCOPY = objcopyifeq ($(ARCH), ia32) LIBDIR := $(PREFIX)/lib ifeq ($(HOSTARCH), x86_64) ARCH3264 := -m32 endifendififeq ($(ARCH), x86_64) CFLAGS += -mno-red-zone LIBDIR := $(PREFIX)/lib64 ifeq ($(HOSTARCH), ia32) ARCH3264 := -m64 endifendifFORMAT=efi-app-$(HOSTARCH)LDFLAGS = -nostdlib -T $(LIBDIR)/gnuefi/elf_$(HOSTARCH)_efi.lds -shared -Bsymbolic $(LIBDIR)/gnuefi/crt0-efi-$(HOSTARCH).o -L$(LIBDIR)LIBS=-lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name)CCLDFLAGS =

For p

erson

nal u

se on

ly

07-13-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 5/6

Page 6: Problems With UEFI Shell Options

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-red-zone -Wimplicit-function-declarationTARGETS = shellopt.efiall : $(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 touse 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 currentLinux kernel codebase (which is over 15 million LOC.) This is erroneous as CLOC reports just over2 million LOC in the current EDK2 trunk. Even so, the UEFI codebase is very large and not thatmany eyeballs have examined the source code in detail. I spotted numerous other lines of iffy codewhile hunting down and resolving the ShellOpt issue. I suspect that there numerous latent bugs inthe codebase which will only come to light as UEFI becomes mainstream.

For p

erson

nal u

se on

ly

07-13-2012 Copyright 2004-2012 Finnbarr P. Murphy. All rights reserved. 6/6