diff options
Diffstat (limited to 'src/boot/efi/meson.build')
-rw-r--r-- | src/boot/efi/meson.build | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build new file mode 100644 index 0000000..0196314 --- /dev/null +++ b/src/boot/efi/meson.build @@ -0,0 +1,500 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +conf.set10('ENABLE_EFI', get_option('efi')) +conf.set10('HAVE_GNU_EFI', false) + +efi_config_h_dir = meson.current_build_dir() + +if not get_option('efi') or get_option('gnu-efi') == 'false' + if get_option('gnu-efi') == 'true' + error('gnu-efi support requested, but general efi support is disabled') + endif + subdir_done() +endif + +efi_arch = host_machine.cpu_family() +if efi_arch == 'x86' and '-m64' in get_option('efi-cflags') + efi_arch = 'x86_64' +elif efi_arch == 'x86_64' and '-m32' in get_option('efi-cflags') + efi_arch = 'x86' +endif +efi_arch = { + # host_cc_arch: [efi_arch (see Table 3-2 in UEFI spec), obsolete gnu_efi_inc_arch] + 'x86': ['ia32', 'ia32'], + 'x86_64': ['x64', 'x86_64'], + 'arm': ['arm', 'arm'], + 'aarch64': ['aa64', 'aarch64'], + 'riscv64': ['riscv64', 'riscv64'], +}.get(efi_arch, []) + +efi_incdir = get_option('efi-includedir') +found = false +foreach efi_arch_candidate : efi_arch + efi_archdir = efi_incdir / efi_arch_candidate + if cc.has_header(efi_archdir / 'efibind.h', + args: get_option('efi-cflags')) + found = true + break + endif +endforeach + +if not found + if get_option('gnu-efi') == 'true' + error('gnu-efi support requested, but headers not found or efi arch is unknown') + endif + warning('gnu-efi headers not found or efi arch is unknown, disabling gnu-efi support') + subdir_done() +endif + +if not cc.has_header_symbol('efi.h', 'EFI_IMAGE_MACHINE_X64', + args: ['-nostdlib', '-ffreestanding', '-fshort-wchar'] + get_option('efi-cflags'), + include_directories: include_directories(efi_incdir, + efi_archdir)) + + if get_option('gnu-efi') == 'true' + error('gnu-efi support requested, but found headers are too old (3.0.5+ required)') + endif + warning('gnu-efi headers are too old (3.0.5+ required), disabling gnu-efi support') + subdir_done() +endif + +objcopy = run_command(cc.cmd_array(), '-print-prog-name=objcopy', check: true).stdout().strip() +objcopy_2_38 = find_program('objcopy', version: '>=2.38', required: false) + +efi_ld = get_option('efi-ld') +if efi_ld == 'auto' + efi_ld = cc.get_linker_id().split('.')[1] + if efi_ld not in ['bfd', 'gold'] + message('Not using @0@ as efi-ld, falling back to bfd'.format(efi_ld)) + efi_ld = 'bfd' + endif +endif + +efi_multilib = run_command( + cc.cmd_array(), '-print-multi-os-directory', get_option('efi-cflags'), + check: false +).stdout().strip() +efi_multilib = run_command( + 'realpath', '-e', '/usr/lib' / efi_multilib, + check: false +).stdout().strip() + +efi_libdir = '' +foreach dir : [get_option('efi-libdir'), + '/usr/lib/gnuefi' / efi_arch[0], + efi_multilib] + if dir != '' and fs.is_dir(dir) + efi_libdir = dir + break + endif +endforeach +if efi_libdir == '' + if get_option('gnu-efi') == 'true' + error('gnu-efi support requested, but efi-libdir was not found') + endif + warning('efi-libdir was not found, disabling gnu-efi support') + subdir_done() +endif + +efi_lds = '' +foreach location : [ # New locations first introduced with gnu-efi 3.0.11 + [efi_libdir / 'efi.lds', + efi_libdir / 'crt0.o'], + # Older locations... + [efi_libdir / 'gnuefi' / 'elf_@0@_efi.lds'.format(efi_arch[1]), + efi_libdir / 'gnuefi' / 'crt0-efi-@0@.o'.format(efi_arch[1])], + [efi_libdir / 'elf_@0@_efi.lds'.format(efi_arch[1]), + efi_libdir / 'crt0-efi-@0@.o'.format(efi_arch[1])]] + if fs.is_file(location[0]) and fs.is_file(location[1]) + efi_lds = location[0] + efi_crt0 = location[1] + break + endif +endforeach +if efi_lds == '' + if get_option('gnu-efi') == 'true' + error('gnu-efi support requested, but cannot find efi.lds') + endif + warning('efi.lds was not found, disabling gnu-efi support') + subdir_done() +endif + +conf.set10('HAVE_GNU_EFI', true) +conf.set_quoted('EFI_MACHINE_TYPE_NAME', efi_arch[0]) + +efi_conf = configuration_data() +efi_conf.set_quoted('EFI_MACHINE_TYPE_NAME', efi_arch[0]) +efi_conf.set10('ENABLE_TPM', get_option('tpm')) +efi_conf.set10('EFI_TPM_PCR_COMPAT', get_option('efi-tpm-pcr-compat')) + +foreach ctype : ['color-normal', 'color-entry', 'color-highlight', 'color-edit'] + c = get_option('efi-' + ctype).split(',') + efi_conf.set(ctype.underscorify().to_upper(), 'EFI_TEXT_ATTR(@0@, @1@)'.format( + 'EFI_' + c[0].strip().underscorify().to_upper(), + 'EFI_' + c[1].strip().underscorify().to_upper())) +endforeach + +if meson.is_cross_build() and get_option('sbat-distro') == 'auto' + warning('Auto detection of SBAT information not supported when cross-building, disabling SBAT.') +elif get_option('sbat-distro') != '' + efi_conf.set_quoted('SBAT_PROJECT', meson.project_name()) + efi_conf.set_quoted('PROJECT_VERSION', meson.project_version()) + efi_conf.set('PROJECT_URL', conf.get('PROJECT_URL')) + if get_option('sbat-distro-generation') < 1 + error('SBAT Distro Generation must be a positive integer') + endif + efi_conf.set('SBAT_DISTRO_GENERATION', get_option('sbat-distro-generation')) + foreach sbatvar : [['sbat-distro', 'ID'], + ['sbat-distro-summary', 'NAME'], + ['sbat-distro-url', 'BUG_REPORT_URL']] + value = get_option(sbatvar[0]) + if (value == '' or value == 'auto') and not meson.is_cross_build() + cmd = 'if [ -e /etc/os-release ]; then . /etc/os-release; else . /usr/lib/os-release; fi; echo $@0@'.format(sbatvar[1]) + value = run_command(sh, '-c', cmd, check: true).stdout().strip() + endif + if value == '' + error('Required @0@ option not set and autodetection failed'.format(sbatvar[0])) + endif + efi_conf.set_quoted(sbatvar[0].underscorify().to_upper(), value) + endforeach + + pkgname = get_option('sbat-distro-pkgname') + if pkgname == '' + pkgname = meson.project_name() + endif + efi_conf.set_quoted('SBAT_DISTRO_PKGNAME', pkgname) + + pkgver = get_option('sbat-distro-version') + if pkgver == '' + efi_conf.set('SBAT_DISTRO_VERSION', 'GIT_VERSION') + # This is determined during build, not configuration, so we can't display it yet. + sbat_distro_version_display = '(git version)' + else + efi_conf.set_quoted('SBAT_DISTRO_VERSION', pkgver) + sbat_distro_version_display = pkgver + endif +endif + +efi_config_h = configure_file( + output : 'efi_config.h', + configuration : efi_conf) + +efi_cflags = [ + '-DGNU_EFI_USE_MS_ABI', + '-DSD_BOOT', + '-ffreestanding', + '-fshort-wchar', + '-fvisibility=hidden', + '-I', fundamental_path, + '-I', meson.current_source_dir(), + '-include', efi_config_h, + '-include', version_h, + '-I', efi_archdir, + '-isystem', efi_incdir, + '-std=gnu11', + '-Wall', + '-Wextra', +] + cc.get_supported_arguments( + basic_disabled_warnings + + possible_common_cc_flags + [ + '-fno-stack-protector', + '-fno-strict-aliasing', + '-fpic', + '-fwide-exec-charset=UCS2', + ] +) + +# Our code size has increased enough to possibly create overlapping PE sections +# at sd-stub runtime, which will often enough prevent the image from booting. +# This only happens because the usual instructions for assembling a unified +# kernel image contain hardcoded addresses for section VMAs added in. Until a +# proper solution is in place, we can at least compile with as least -O1 to +# reduce the likelihood of this happening. +# https://github.com/systemd/systemd/issues/24202 +efi_cflags += '-O1' + +efi_cflags += cc.get_supported_arguments({ + 'ia32': ['-mno-sse', '-mno-mmx'], + 'x86_64': ['-mno-red-zone', '-mno-sse', '-mno-mmx'], + 'arm': ['-mgeneral-regs-only', '-mfpu=none'], +}.get(efi_arch[1], [])) + +# We are putting the efi_cc command line together ourselves, so make sure to pull any +# relevant compiler flags from meson/CFLAGS as povided by the user or distro. + +if get_option('werror') + efi_cflags += ['-Werror'] +endif +if get_option('debug') and get_option('mode') == 'developer' + efi_cflags += ['-ggdb', '-DEFI_DEBUG'] +endif +if get_option('optimization') in ['1', '2', '3', 's', 'g'] + efi_cflags += ['-O' + get_option('optimization')] +endif +if get_option('b_ndebug') == 'true' or ( + get_option('b_ndebug') == 'if-release' and get_option('buildtype') in ['plain', 'release']) + efi_cflags += ['-DNDEBUG'] +endif +if get_option('b_lto') + efi_cflags += cc.has_argument('-flto=auto') ? ['-flto=auto'] : ['-flto'] +endif + +foreach arg : get_option('c_args') + if arg in [ + '-DNDEBUG', + '-fno-lto', + '-O1', '-O2', '-O3', '-Og', '-Os', + '-Werror', + ] or arg.split('=')[0] in [ + '-ffile-prefix-map', + '-flto', + ] or (get_option('mode') == 'developer' and arg in [ + '-DEFI_DEBUG', + '-g', '-ggdb', + ]) + + message('Using "@0@" from c_args for EFI compiler'.format(arg)) + efi_cflags += arg + endif +endforeach + +efi_cflags += get_option('efi-cflags') + +efi_ldflags = [ + '-fuse-ld=' + efi_ld, + '-L', efi_libdir, + '-nostdlib', + '-T', efi_lds, + '-Wl,--build-id=sha1', + '-Wl,--fatal-warnings', + '-Wl,--no-undefined', + '-Wl,--warn-common', + '-Wl,-Bsymbolic', + '-z', 'nocombreloc', + '-z', 'noexecstack', + efi_crt0, +] + +foreach arg : ['-Wl,--no-warn-execstack', + '-Wl,--no-warn-rwx-segments'] + # We need to check the correct linker for supported args. This is what + # cc.has_multi_link_arguments() is for, but it helpfully overrides our + # choice of linker by putting its own -fuse-ld= arg after ours. + if run_command('bash', '-c', + 'exec "$@" -x c -o/dev/null <(echo "int main(void){return 0;}")' + + ' -fuse-ld=' + efi_ld + ' -Wl,--fatal-warnings ' + arg, + 'bash', cc.cmd_array(), + check : false).returncode() == 0 + efi_ldflags += arg + endif +endforeach + +# If using objcopy, crt0 must not include the PE/COFF header +if run_command('grep', '-q', 'coff_header', efi_crt0, check: false).returncode() == 0 + coff_header_in_crt0 = true +else + coff_header_in_crt0 = false +endif + +if efi_arch[1] in ['arm', 'riscv64'] or (efi_arch[1] == 'aarch64' and (not objcopy_2_38.found() or coff_header_in_crt0)) + efi_ldflags += ['-shared'] + # ARM32 and 64bit RISC-V don't have an EFI capable objcopy. + # Older objcopy doesn't support Aarch64 either. + # Use 'binary' instead, and add required symbols manually. + efi_ldflags += ['-Wl,--defsym=EFI_SUBSYSTEM=0xa'] + efi_format = ['-O', 'binary'] +else + efi_ldflags += ['-pie'] + if efi_ld == 'bfd' + efi_ldflags += '-Wl,--no-dynamic-linker' + endif + efi_format = ['--target=efi-app-@0@'.format(efi_arch[1])] +endif + +if efi_arch[1] == 'arm' + # On arm, the compiler (correctly) warns about wchar_t size mismatch. This + # is because libgcc is not compiled with -fshort-wchar, but it does not + # have any occurrences of wchar_t in its sources or the documentation, so + # it is safe to assume that we can ignore this warning. + efi_ldflags += ['-Wl,--no-wchar-size-warning'] +endif + +if run_command('grep', '-q', '__CTOR_LIST__', efi_lds, check: false).returncode() == 0 + # fedora has a patched gnu-efi that adds support for ELF constructors. + # If ld is called by gcc something about these symbols breaks, resulting + # in sd-boot freezing when gnu-efi runs the constructors. Force defining + # them seems to work around this. + efi_ldflags += [ + '-Wl,--defsym=_init_array=0', + '-Wl,--defsym=_init_array_end=0', + '-Wl,--defsym=_fini_array=0', + '-Wl,--defsym=_fini_array_end=0', + '-Wl,--defsym=__CTOR_LIST__=0', + '-Wl,--defsym=__CTOR_END__=0', + '-Wl,--defsym=__DTOR_LIST__=0', + '-Wl,--defsym=__DTOR_END__=0', + ] +endif + +if cc.get_id() == 'clang' and cc.version().split('.')[0].to_int() <= 10 + # clang <= 10 doesn't pass -T to the linker and then even complains about it being unused + efi_ldflags += ['-Wl,-T,' + efi_lds, '-Wno-unused-command-line-argument'] +endif + +summary({ + 'EFI machine type' : efi_arch[0], + 'EFI LD' : efi_ld, + 'EFI lds' : efi_lds, + 'EFI crt0' : efi_crt0, + 'EFI include directory' : efi_archdir}, + section : 'Extensible Firmware Interface') + +if efi_conf.get('SBAT_DISTRO', '') != '' + summary({ + 'SBAT distro': efi_conf.get('SBAT_DISTRO'), + 'SBAT distro generation': efi_conf.get('SBAT_DISTRO_GENERATION'), + 'SBAT distro version': sbat_distro_version_display, + 'SBAT distro summary': efi_conf.get('SBAT_DISTRO_SUMMARY'), + 'SBAT distro URL': efi_conf.get('SBAT_DISTRO_URL')}, + section : 'Extensible Firmware Interface') +endif + +############################################################ + +efi_headers = files( + 'bcd.h', + 'console.h', + 'cpio.h', + 'devicetree.h', + 'disk.h', + 'drivers.h', + 'efi-string.h', + 'graphics.h', + 'initrd.h', + 'linux.h', + 'measure.h', + 'missing_efi.h', + 'pe.h', + 'random-seed.h', + 'secure-boot.h', + 'shim.h', + 'splash.h', + 'ticks.h', + 'util.h', + 'xbootldr.h', +) + +common_sources = files( + 'assert.c', + 'console.c', + 'devicetree.c', + 'disk.c', + 'efi-string.c', + 'graphics.c', + 'initrd.c', + 'measure.c', + 'pe.c', + 'secure-boot.c', + 'ticks.c', + 'util.c', +) + +systemd_boot_sources = files( + 'boot.c', + 'drivers.c', + 'random-seed.c', + 'vmm.c', + 'shim.c', + 'xbootldr.c', +) + +stub_sources = files( + 'cpio.c', + 'linux.c', + 'splash.c', + 'stub.c', +) + +if efi_arch[1] in ['ia32', 'x86_64'] + stub_sources += files('linux_x86.c') +endif + +tests += [ + [files('test-efi-string.c', 'efi-string.c')], +] + +# BCD parser only makes sense on arches that Windows supports. +if efi_arch[1] in ['ia32', 'x86_64', 'arm', 'aarch64'] + systemd_boot_sources += files('bcd.c') + tests += [ + [files('test-bcd.c', 'efi-string.c'), + [], + [libzstd], + [], + 'HAVE_ZSTD'], + ] + fuzzers += [ + [files('fuzz-bcd.c', 'bcd.c', 'efi-string.c')], + [files('fuzz-efi-string.c', 'efi-string.c')], + ] +endif + +systemd_boot_objects = [] +stub_objects = [] +foreach file : fundamental_source_paths + common_sources + systemd_boot_sources + stub_sources + # FIXME: replace ''.format(file) with fs.name(file) when meson_version requirement is >= 0.59.0 + o_file = custom_target('@0@.o'.format(file).split('/')[-1], + input : file, + output : '@0@.o'.format(file).split('/')[-1], + command : [cc.cmd_array(), '-c', '@INPUT@', '-o', '@OUTPUT@', efi_cflags], + depend_files : efi_headers + fundamental_headers) + if (fundamental_source_paths + common_sources + systemd_boot_sources).contains(file) + systemd_boot_objects += o_file + endif + if (fundamental_source_paths + common_sources + stub_sources).contains(file) + stub_objects += o_file + endif +endforeach + +foreach tuple : [['systemd-boot@0@.@1@', systemd_boot_objects, false, 'systemd-boot'], + ['linux@0@.@1@.stub', stub_objects, true, 'systemd-stub']] + elf = custom_target( + tuple[0].format(efi_arch[0], 'elf'), + input : tuple[1], + output : tuple[0].format(efi_arch[0], 'elf'), + command : [cc.cmd_array(), + '-o', '@OUTPUT@', + efi_cflags, + efi_ldflags, + '@INPUT@', + '-lefi', + '-lgnuefi', + '-lgcc'], + install : tuple[2], + install_tag: tuple[3], + install_dir : bootlibdir) + + efi = custom_target( + tuple[0].format(efi_arch[0], 'efi'), + input : elf, + output : tuple[0].format(efi_arch[0], 'efi'), + command : [objcopy, + '-j', '.bss*', + '-j', '.data', + '-j', '.dynamic', + '-j', '.dynsym', + '-j', '.osrel', + '-j', '.rel*', + '-j', '.sbat', + '-j', '.sdata', + '-j', '.sdmagic', + '-j', '.text', + '--section-alignment=512', + efi_format, + '@INPUT@', '@OUTPUT@'], + install : true, + install_tag: tuple[3], + install_dir : bootlibdir) + + alias_target(tuple[3], efi) +endforeach |