diff --git a/LibOS/shim/include/shim_defs.h b/LibOS/shim/include/shim_defs.h index 8c4789f7..031397da 100644 --- a/LibOS/shim/include/shim_defs.h +++ b/LibOS/shim/include/shim_defs.h @@ -35,7 +35,7 @@ #define MIGRATE_SYSV_MSG 1 /* ELF aux vectors */ -#define REQUIRED_ELF_AUXV 7 /* number of LibOS-supported vectors */ +#define REQUIRED_ELF_AUXV 8 /* number of LibOS-supported vectors */ #define REQUIRED_ELF_AUXV_SPACE 16 /* extra memory space (in bytes) */ #endif /* _SHIM_DEFS_H_ */ diff --git a/LibOS/shim/include/shim_vdso.h b/LibOS/shim/include/shim_vdso.h new file mode 100644 index 00000000..b1a32a99 --- /dev/null +++ b/LibOS/shim/include/shim_vdso.h @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Intel Corporation. + * Copyright 2018 Isaku Yamahata + * + * All Rights Reserved. + * + * This file is part of Graphene Library OS. + * + * Graphene Library OS is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Graphene Library OS 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef _SHIM_VDSO_H_ +#define _SHIM_VDSO_H_ + +extern const uint8_t vdso_so[]; +extern const size_t vdso_so_size; + +int vdso_map_migrate(void); + +#endif /* _SHIM_VDSO_H_ */ diff --git a/LibOS/shim/src/Makefile b/LibOS/shim/src/Makefile index 6a0f08fa..952b8ab5 100644 --- a/LibOS/shim/src/Makefile +++ b/LibOS/shim/src/Makefile @@ -49,7 +49,9 @@ objs = $(addprefix bookkeep/shim_,handle vma thread signal) \ elf/shim_rtld \ $(addprefix shim_,init table syscalls checkpoint malloc \ async parser debug object) syscallas start \ - $(patsubst %.c,%,$(wildcard sys/*.c)) + $(patsubst %.c,%,$(wildcard sys/*.c)) \ + vdso/vdso-data + graphene_lib = .lib/graphene-lib.a pal_lib = $(RUNTIME_DIR)/libpal-$(PAL_HOST).so headers = ../include/*.h ../../../Pal/lib/*.h ../../../Pal/include/pal/*.h @@ -120,5 +122,21 @@ syscallas.S shim_checkpoint.c: asm-offsets.h include ../../../Makefile.rules +LDFLAGS-vdso/vdso.so.dbg = -nostdlib -shared \ + --hash-style=both --build-id -Bsymbolic \ + -m elf_x86_64 --no-undefined \ + -z max-page-size=4096 -z common-page-size=4096 \ + -T vdso/vdso.lds -soname linux-vdso.so.1 +vdso/vdso.so.dbg: LDFLAGS = +vdso/vdso.so.dbg: vdso/vdso.lds vdso/vdso.o vdso/vdso-note.o + $(call cmd,ld) + +OBJCOPYFLAGS-vdso/vdso.so = -S +vdso/vdso.so: vdso/vdso.so.dbg + $(call cmd,objcopy) + +vdso/vdso-data.o: vdso/vdso.so +CLEAN_FILES += vdso/vdso.so.dbg vdso/vdso.so + clean: rm -rf $(addsuffix .o,$(objs)) $(shim_target) $(files_to_build) .lib $(CLEAN_FILES) diff --git a/LibOS/shim/src/elf/shim_rtld.c b/LibOS/shim/src/elf/shim_rtld.c index 2724f972..2fd3af7f 100644 --- a/LibOS/shim/src/elf/shim_rtld.c +++ b/LibOS/shim/src/elf/shim_rtld.c @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -55,6 +56,7 @@ enum object_type { OBJECT_MAPPED = 2, OBJECT_REMAP = 3, OBJECT_USER = 4, + OBJECT_VDSO = 5, }; /* Structure describing a loaded shared object. The `l_next' and `l_prev' @@ -147,6 +149,7 @@ struct link_map * lookup_symbol (const char * undef_name, ElfW(Sym) ** ref); static struct link_map * loaded_libraries = NULL; static struct link_map * internal_map = NULL, * interp_map = NULL; +static struct link_map * vdso_map = NULL; /* This macro is used as a callback from the ELF_DYNAMIC_RELOCATE code. */ static ElfW(Addr) resolve_map (const char ** strtab, ElfW(Sym) ** ref) @@ -376,7 +379,7 @@ __map_elf_object (struct shim_handle * file, const char * errstring __attribute__((unused)) = NULL; int ret; - if (type != OBJECT_INTERNAL && !file) { + if (type != OBJECT_INTERNAL && type != OBJECT_VDSO && !file) { errstring = "shared object has to be backed by file"; goto call_lose; } @@ -589,9 +592,9 @@ do_remap: } #if BOOKKEEP_INTERNAL_OBJ == 0 - if (type != OBJECT_INTERNAL && type != OBJECT_USER) + if (type != OBJECT_INTERNAL && type != OBJECT_USER && type != OBJECT_VDSO) #else - if (type != OBJECT_USER) + if (type != OBJECT_USER && type != OBJECT_VDSO) #endif bkeep_mmap(mapaddr, c->mapend - c->mapstart, c->prot, c->flags|MAP_FIXED|MAP_PRIVATE| @@ -623,7 +626,9 @@ postmap: if (type != OBJECT_MAPPED && type != OBJECT_INTERNAL && - type != OBJECT_USER && zeropage > zero) { + type != OBJECT_USER && + type != OBJECT_VDSO && + zeropage > zero) { /* Zero the final part of the last page of the segment. */ if (__builtin_expect ((c->prot & PROT_WRITE) == 0, 0)) { /* Dag nab it. */ @@ -647,7 +652,8 @@ postmap: if (zeroend > zeropage) { if (type != OBJECT_MAPPED && type != OBJECT_INTERNAL && - type != OBJECT_USER) { + type != OBJECT_USER && + type != OBJECT_VDSO) { PAL_PTR mapat = DkVirtualMemoryAlloc((void *)zeropage, zeroend - zeropage, 0, c->prot); if (__builtin_expect (!mapat, 0)) { @@ -657,9 +663,9 @@ postmap: } #if BOOKKEEP_INTERNAL_OBJ == 0 - if (type != OBJECT_INTERNAL && type != OBJECT_USER) + if (type != OBJECT_INTERNAL && type != OBJECT_USER && type != OBJECT_VDSO) #else - if (type != OBJECT_USER) + if (type != OBJECT_USER && type != OBJECT_VDSO) #endif bkeep_mmap((void *) zeropage, zeroend - zeropage, c->prot, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED| @@ -1057,7 +1063,7 @@ static int __load_elf_object (struct shim_handle * file, void * addr, goto out; } - if (type != OBJECT_INTERNAL) + if (type != OBJECT_INTERNAL && type != OBJECT_VDSO) do_relocate_object(map); if (internal_map) { @@ -1067,6 +1073,8 @@ static int __load_elf_object (struct shim_handle * file, void * addr, if (type == OBJECT_INTERNAL) internal_map = map; + if (type == OBJECT_VDSO) + vdso_map = map; if (type != OBJECT_REMAP) { if (file) { @@ -1261,14 +1269,21 @@ do_lookup_map (ElfW(Sym) * ref, const char * undef_name, /* Inner part of the lookup functions. We return a value > 0 if we found the symbol, the value 0 if nothing is found and < 0 if something bad happened. */ -static int do_lookup (const char * undef_name, ElfW(Sym) * ref, - struct sym_val * result) +static ElfW(Sym) * +__do_lookup (const char * undef_name, ElfW(Sym) * ref, + struct link_map * map) { const uint_fast32_t fast_hash = elf_fast_hash(undef_name); const long int hash = elf_hash(undef_name); + return do_lookup_map(ref, undef_name, fast_hash, hash, map); +} + +static int do_lookup (const char * undef_name, ElfW(Sym) * ref, + struct sym_val * result) +{ ElfW(Sym) *sym = NULL; - sym = do_lookup_map(ref, undef_name, fast_hash, hash, internal_map); + sym = __do_lookup(undef_name, ref, internal_map); if (!sym) return 0; @@ -1463,7 +1478,7 @@ int remove_loaded_libraries (void) { struct link_map * map = loaded_libraries, * next_map = map->l_next; while (map) { - if (map->l_type != OBJECT_INTERNAL) + if (map->l_type != OBJECT_INTERNAL && map->l_type != OBJECT_VDSO) __remove_elf_object(map); map = next_map; @@ -1473,6 +1488,107 @@ int remove_loaded_libraries (void) return 0; } +/* + * libsysdb.so is loaded as shared library and load address for child may not match the one for + * parent. Just treat vdso page as user-program data and adjust function pointers for vdso + * functions after migration. + */ +static void * vdso_addr __attribute_migratable = NULL; +static ElfW(Addr) * __vdso_shim_clock_gettime __attribute_migratable = NULL; +static ElfW(Addr) * __vdso_shim_gettimeofday __attribute_migratable = NULL; +static ElfW(Addr) * __vdso_shim_time __attribute_migratable = NULL; +static ElfW(Addr) * __vdso_shim_getcpu __attribute_migratable = NULL; + +static const struct { + const char *name; + ElfW(Addr) value; + ElfW(Addr) ** func; +} vsyms[] = { + { + .name = "__vdso_shim_clock_gettime", + .value = (ElfW(Addr))&__shim_clock_gettime, + .func = &__vdso_shim_clock_gettime, + }, + { + .name = "__vdso_shim_gettimeofday", + .value = (ElfW(Addr))&__shim_gettimeofday, + .func = &__vdso_shim_gettimeofday, + }, + { + .name = "__vdso_shim_time", + .value = (ElfW(Addr))&__shim_time, + .func = &__vdso_shim_time, + }, + { + .name = "__vdso_shim_getcpu", + .value = (ElfW(Addr))&__shim_getcpu, + .func = &__vdso_shim_getcpu, + } +}; + +static int vdso_map_init(void) +{ + /* + * Allocate vdso page as user program allocated it. + * Using directly vdso code in LibOS causes trouble when emulating fork. + * In host child process, LibOS may or may not be loaded at the same address. + * When LibOS is loaded at different address, it may overlap with the old vDSO area. + */ + void *addr = bkeep_unmapped_heap( + ALIGN_UP(vdso_so_size), PROT_READ | PROT_EXEC, 0, NULL, 0, + "linux-vdso.so.1"); + if (addr == NULL) + return -ENOMEM; + assert(addr == ALIGN_UP(addr)); + + void *ret_addr = (void *)DkVirtualMemoryAlloc( + addr, ALIGN_UP(vdso_so_size), 0, PAL_PROT_READ | PAL_PROT_WRITE); + if (!ret_addr) + return -PAL_ERRNO; + assert(addr == ret_addr); + + memcpy(addr, &vdso_so, vdso_so_size); + memset(addr + vdso_so_size, 0, ALIGN_UP(vdso_so_size) - vdso_so_size); + __load_elf_object(NULL, addr, OBJECT_VDSO, NULL); + vdso_map->l_name = "vDSO"; + + for (size_t i = 0; i < sizeof(vsyms)/sizeof(vsyms[0]); i++) { + ElfW(Sym) *sym = __do_lookup(vsyms[i].name, NULL, vdso_map); + if (sym == NULL) { + debug("vDSO: symbol value for %s not found\n", vsyms[i].name); + continue; + } + *vsyms[i].func = (ElfW(Addr)*)(vdso_map->l_addr + sym->st_value); + **vsyms[i].func = vsyms[i].value; + } + + if (!DkVirtualMemoryProtect(addr, ALIGN_UP(vdso_so_size), + PAL_PROT_READ | PAL_PROT_EXEC)) + return -PAL_ERRNO; + + vdso_addr = addr; + return 0; +} + +int vdso_map_migrate(void) +{ + if (!vdso_addr) + return 0; + + if (!DkVirtualMemoryProtect(vdso_addr, ALIGN_UP(vdso_so_size), + PAL_PROT_READ | PAL_PROT_WRITE)) + return -PAL_ERRNO; + + /* adjust funcs to loaded address for newly loaded libsysdb */ + for (size_t i = 0; i < sizeof(vsyms)/sizeof(vsyms[0]); i++) + **vsyms[i].func = vsyms[i].value; + + if (!DkVirtualMemoryProtect(vdso_addr, ALIGN_UP(vdso_so_size), + PAL_PROT_READ | PAL_PROT_EXEC)) + return -PAL_ERRNO; + return 0; +} + int init_internal_map (void) { __load_elf_object(NULL, &__load_address, OBJECT_INTERNAL, NULL); @@ -1562,14 +1678,18 @@ noreturn void execute_elf_object (struct shim_handle * exec, int * argcp, const char ** argp, ElfW(auxv_t) * auxp) { + int ret = vdso_map_init(); + if (ret < 0) { + SYS_PRINTF("Could not initialize vDSO (error code = %d)", ret); + shim_clean(ret); + } + struct link_map * exec_map = __search_map_by_handle(exec); assert(exec_map); assert((uintptr_t)argcp % 16 == 0); /* stack must be 16B-aligned */ assert((void*)argcp + sizeof(long) == argp || argp == NULL); - /* populate ELF aux vectors */ - assert(REQUIRED_ELF_AUXV >= 7); /* stack allocated enough space */ - + assert(REQUIRED_ELF_AUXV >= 8); /* stack allocated enough space */ auxp[0].a_type = AT_PHDR; auxp[0].a_un.a_val = (__typeof(auxp[0].a_un.a_val)) exec_map->l_phdr; auxp[1].a_type = AT_PHNUM; @@ -1582,15 +1702,22 @@ noreturn void execute_elf_object (struct shim_handle * exec, auxp[4].a_un.a_val = interp_map ? interp_map->l_addr : 0; auxp[5].a_type = AT_RANDOM; auxp[5].a_un.a_val = 0; /* filled later */ - auxp[6].a_type = AT_NULL; - auxp[6].a_un.a_val = 0; + if (vdso_addr) { + auxp[6].a_type = AT_SYSINFO_EHDR; + auxp[6].a_un.a_val = (uint64_t)vdso_addr; + } else { + auxp[6].a_type = AT_NULL; + auxp[6].a_un.a_val = 0; + } + auxp[7].a_type = AT_NULL; + auxp[7].a_un.a_val = 0; /* populate extra memory space for aux vector data */ assert(REQUIRED_ELF_AUXV_SPACE >= 16); /* stack allocated enough space */ - ElfW(Addr) auxp_extra = (ElfW(Addr))&auxp[7]; + ElfW(Addr) auxp_extra = (ElfW(Addr))&auxp[8]; ElfW(Addr) random = auxp_extra; /* random 16B for AT_RANDOM */ - int ret = DkRandomBitsRead((PAL_PTR)random, 16); + ret = DkRandomBitsRead((PAL_PTR)random, 16); if (ret < 0) { debug("execute_elf_object: DkRandomBitsRead failed.\n"); DkThreadExit(); diff --git a/LibOS/shim/src/shim_init.c b/LibOS/shim/src/shim_init.c index bb07f083..4bb38d5d 100644 --- a/LibOS/shim/src/shim_init.c +++ b/LibOS/shim/src/shim_init.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -801,8 +802,10 @@ restore: shim_tcb_t * cur_tcb = shim_get_tls(); struct shim_thread * cur_thread = (struct shim_thread *) cur_tcb->tp; - if (cur_tcb->context.regs && cur_tcb->context.regs->rsp) + if (cur_tcb->context.regs && cur_tcb->context.regs->rsp) { + vdso_map_migrate(); restore_context(&cur_tcb->context); + } if (cur_thread->exec) execute_elf_object(cur_thread->exec, argcp, argp, auxp); diff --git a/LibOS/shim/src/vdso/.gitignore b/LibOS/shim/src/vdso/.gitignore new file mode 100644 index 00000000..dfaf50d0 --- /dev/null +++ b/LibOS/shim/src/vdso/.gitignore @@ -0,0 +1,2 @@ +/vdso-data.c +/vdso.so.dbg diff --git a/LibOS/shim/src/vdso/vdso-data.S b/LibOS/shim/src/vdso/vdso-data.S new file mode 100644 index 00000000..54c23730 --- /dev/null +++ b/LibOS/shim/src/vdso/vdso-data.S @@ -0,0 +1,19 @@ + .section .rodata + + .global vdso_so + .type vdso_so, @object + .align 4 +vdso_so: + .incbin "vdso/vdso.so" + + .global vdso_so_end + .type vdso_so_end, @object + .align 4 +vdso_so_end: + + .global vdso_so_size + .type vdso_so_size, @object + .size vdso_so_size, 8 + .align 8 +vdso_so_size: + .quad vdso_so_end - vdso_so diff --git a/LibOS/shim/src/vdso/vdso-note.S b/LibOS/shim/src/vdso/vdso-note.S new file mode 100644 index 00000000..545bc476 --- /dev/null +++ b/LibOS/shim/src/vdso/vdso-note.S @@ -0,0 +1,19 @@ + /* This .note section informs dynamic linker about vDSO */ + .section .note.Linux, "a", @note + + .balign 4 + .long 6 /* namesz */ + .long 4 /* descsz */ + .long 0 /* type */ + .string "Linux" /* name */ + .zero 2 /* padding for 4-byte alignment */ + .long 267008 /* LINUX_VERSION_CODE */ + + .balign 4 + .long 6 /* namesz */ + .long 4 /* descsz */ + .long 0x100 /* type */ + .string "Linux" /* name */ + .zero 2 /* padding for 4-byte alignment */ + .long 0 /* CONFIG_BUILD_SALT. 0 for now. + TODO: make it compile-time configurable */ diff --git a/LibOS/shim/src/vdso/vdso.c b/LibOS/shim/src/vdso/vdso.c new file mode 100644 index 00000000..a689ea52 --- /dev/null +++ b/LibOS/shim/src/vdso/vdso.c @@ -0,0 +1,63 @@ +/* Copyright (C) 2018 Intel Corporation + Isaku Yamahata + + All Rights Reserved. + + This file is part of Graphene Library OS. + + Graphene Library OS is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + Graphene Library OS 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#include + +int (*__vdso_shim_clock_gettime)(clockid_t clock, struct timespec *t) = NULL; +int (*__vdso_shim_gettimeofday)(struct timeval *tv, struct timezone *tz) = NULL; +time_t (*__vdso_shim_time)(time_t *t) = NULL; +long (*__vdso_shim_getcpu)(unsigned *cpu, struct getcpu_cache *unused) = NULL; + + +int __vdso_clock_gettime(clockid_t clock, struct timespec *t) +{ + if (__vdso_shim_clock_gettime) + return (*__vdso_shim_clock_gettime)(clock, t); + return -ENOSYS; +} +int clock_gettime(clockid_t clock, struct timespec *t) + __attribute__((weak, alias("__vdso_clock_gettime"))); + +int __vdso_gettimeofday(struct timeval *tv, struct timezone *tz) +{ + if (__vdso_shim_gettimeofday) + return (*__vdso_shim_gettimeofday)(tv, tz); + return -ENOSYS; +} +int gettimeofday(struct timeval *tv, struct timezone *tz) + __attribute__((weak, alias("__vdso_gettimeofday"))); + +time_t __vdso_time(time_t *t) +{ + if (__vdso_shim_time) + return (*__vdso_shim_time)(t); + return -ENOSYS; +} +time_t time(time_t *t) __attribute__((weak, alias("__vdso_time"))); + +long __vdso_getcpu(unsigned *cpu, struct getcpu_cache *unused) +{ + if (__vdso_shim_getcpu) + return (*__vdso_shim_getcpu)(cpu, unused); + return -ENOSYS; +} +long getcpu(unsigned *cpu, struct getcpu_cache *unused) + __attribute__((weak, alias("__vdso_getcpu"))); diff --git a/LibOS/shim/src/vdso/vdso.lds b/LibOS/shim/src/vdso/vdso.lds new file mode 100644 index 00000000..f278c015 --- /dev/null +++ b/LibOS/shim/src/vdso/vdso.lds @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018 Intel Corporation + * Isaku Yamahata + * + * All Rights Reserved. + * This file is part of Graphene Library OS. + * + * Graphene Library OS is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Graphene Library OS 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Linker script for Graphene vDSO emulation + */ + +PHDRS +{ + text PT_LOAD FLAGS(5) FILEHDR PHDRS; + dynamic PT_DYNAMIC FLAGS(4); + note PT_NOTE FLAGS(4); + eh_frame_header PT_GNU_EH_FRAME; +} + +SECTIONS +{ + . = SIZEOF_HEADERS; + .hash : { *(.hash) } :text + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .dynamic : { *(.dynamic) } :text :dynamic + .rodata : { + *(.rodata*) + *(.data*) + *(.sdata*) + *(.got.plt) *(.got) + *(.gnu.linkonce.d.*) + *(.bss*) + *(.dynbss*) + *(.gnu.linkonce.b.*) + } : text + .note : { *(.note.*) } :text :note + .eh_frame_hdr : { *(.eh_frame_hdr) } :text :eh_frame_hdr + .eh_frame : { KEEP (*(.eh_frame)) } :text + .text : { *(.text*) } : text = 0x909090 + + /DISCARD/ : { + *(.discard) + *(.discard.*) + } +} + +VERSION { + LINUX_2.6 { + global: + clock_gettime; + __vdso_clock_gettime; + gettimeofday; + __vdso_gettimeofday; + getcpu; + __vdso_getcpu; + time; + __vdso_time; + + __vdso_shim_clock_gettime; + __vdso_shim_gettimeofday; + __vdso_shim_getcpu; + __vdso_shim_time; + local: *; + }; +} diff --git a/Makefile.configs b/Makefile.configs index 3da199bc..71c491a7 100644 --- a/Makefile.configs +++ b/Makefile.configs @@ -10,3 +10,4 @@ endif ifeq ($(origin LD),default) LD = ld endif +OBJCOPY ?= objcopy diff --git a/Makefile.rules b/Makefile.rules index 777f8108..b4a47aae 100644 --- a/Makefile.rules +++ b/Makefile.rules @@ -69,13 +69,11 @@ CLEAN_FILES += generated_offsets.py generated_offsets.pyc quiet_cmd_ln_sf = [ $@ ] cmd_ln_sf = ln -sf $(abspath $<) $@ - cc-option = $(shell set -e; \ if ($(CC) -Werror $(1) -c -x c /dev/null -o /dev/null) >/dev/null 2>&1; \ then echo "$(1)"; \ fi) - # .c quiet_cmd_cc_o_c = [ $@ ] cmd_cc_o_c = $(CC) $(CFLAGS) $(CFLAGS-$@) -c -o $@ $< @@ -103,3 +101,11 @@ quiet_cmd_ar_so_o = [ $@ ] quiet_cmd_ar_a_o = [ $@ ] cmd_ar_a_o = $(AR) $(ARFLAGS) $(ARFLAGS-$@) $@ $^ + +# LD only +quiet_cmd_ld = [ $@ ] + cmd_ld = $(LD) $(LDFLAGS) $(LDFLAGS-$@) -o $@ $(filter-out %.map %.lds,$^) + +# OBJCOPY +quiet_cmd_objcopy = [ $@ ] + cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS-$@) $< $@