mirror of https://github.com/ipxe/ipxe.git
[build] Fix the REQUIRE_SYMBOL mechanism
At some point in the past few years, binutils became more aggressive at removing unused symbols. To function as a symbol requirement, a relocation record must now be in a section marked with @progbits and must not be in a section which gets discarded during the link (either via --gc-sections or via /DISCARD/). Update REQUIRE_SYMBOL() to generate relocation records meeting these criteria. To minimise the impact upon the final binary size, we use existing symbols (specified via the REQUIRING_SYMBOL() macro) as the relocation targets where possible. We use R_386_NONE or R_X86_64_NONE relocation types to prevent any actual unwanted relocation taking place. Where no suitable symbol exists for REQUIRING_SYMBOL() (such as in config.c), the macro PROVIDE_REQUIRING_SYMBOL() can be used to generate a one-byte-long symbol to act as the relocation target. If there are versions of binutils for which this approach fails, then the fallback will probably involve killing off REQUEST_SYMBOL(), redefining REQUIRE_SYMBOL() to use the current definition of REQUEST_SYMBOL(), and postprocessing the linked ELF file with something along the lines of "nm -u | wc -l" to check that there are no undefined symbols remaining. Signed-off-by: Michael Brown <mcb30@ipxe.org>pull/34/head
parent
86ae6e6c18
commit
fbc4ba4b4e
|
@ -3,6 +3,9 @@
|
|||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/** Dummy relocation type */
|
||||
#define RELOC_TYPE_NONE R_386_NONE
|
||||
|
||||
#ifndef ASSEMBLY
|
||||
|
||||
/** Declare a function with standard calling conventions */
|
||||
|
|
|
@ -346,6 +346,7 @@ int pxe_start_nbp ( void ) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
REQUIRING_SYMBOL ( pxe_api_call );
|
||||
REQUIRE_OBJECT ( pxe_preboot );
|
||||
REQUIRE_OBJECT ( pxe_undi );
|
||||
REQUIRE_OBJECT ( pxe_udp );
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL )
|
||||
|
||||
/* Provide the PXENV_FILE_EXIT_HOOK API call */
|
||||
REQUIRING_SYMBOL ( _kkkpxe_start )
|
||||
REQUIRE_OBJECT ( pxe_exit_hook )
|
||||
|
||||
#define PXELOADER_KEEP_UNDI
|
||||
|
|
|
@ -875,5 +875,8 @@ wait_for_tick:
|
|||
ret
|
||||
.size wait_for_tick, . - wait_for_tick
|
||||
|
||||
/* Drag in objects via _rom_start */
|
||||
REQUIRING_SYMBOL ( _rom_start )
|
||||
|
||||
/* Drag in ROM configuration */
|
||||
REQUIRE_OBJECT ( config_romprefix )
|
||||
|
|
|
@ -118,4 +118,5 @@ struct self_test librm_test __self_test = {
|
|||
.exec = librm_test_exec,
|
||||
};
|
||||
|
||||
REQUIRING_SYMBOL ( librm_test );
|
||||
REQUIRE_OBJECT ( test );
|
||||
|
|
|
@ -590,5 +590,8 @@ struct root_device hv_root_device __root_device = {
|
|||
.driver = &hv_root_driver,
|
||||
};
|
||||
|
||||
/* Drag in objects via hv_root_device */
|
||||
REQUIRING_SYMBOL ( hv_root_device );
|
||||
|
||||
/* Drag in netvsc driver */
|
||||
REQUIRE_OBJECT ( netvsc );
|
||||
|
|
|
@ -496,5 +496,8 @@ struct pci_driver hvm_driver __pci_driver = {
|
|||
.remove = hvm_remove,
|
||||
};
|
||||
|
||||
/* Drag in objects via hvm_driver */
|
||||
REQUIRING_SYMBOL ( hvm_driver );
|
||||
|
||||
/* Drag in netfront driver */
|
||||
REQUIRE_OBJECT ( netfront );
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#ifndef _BITS_COMPILER_H
|
||||
#define _BITS_COMPILER_H
|
||||
|
||||
/** Dummy relocation type */
|
||||
#define RELOC_TYPE_NONE R_X86_64_NONE
|
||||
|
||||
#ifndef ASSEMBLY
|
||||
|
||||
/** Declare a function with standard calling conventions */
|
||||
|
|
|
@ -44,6 +44,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|||
* in the final iPXE executable built.
|
||||
*/
|
||||
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
|
||||
/*
|
||||
* Drag in all requested console types
|
||||
*
|
||||
|
|
|
@ -29,6 +29,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|||
*
|
||||
*/
|
||||
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
|
||||
/*
|
||||
* Drag in Ethernet-specific protocols
|
||||
*/
|
||||
|
|
|
@ -29,6 +29,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|||
*
|
||||
*/
|
||||
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
|
||||
/*
|
||||
* Drag in Fibre Channel-specific commands
|
||||
*
|
||||
|
|
|
@ -29,6 +29,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|||
*
|
||||
*/
|
||||
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
|
||||
/*
|
||||
* Drag in Infiniband-specific protocols
|
||||
*/
|
||||
|
|
|
@ -25,6 +25,8 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
|||
*
|
||||
*/
|
||||
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
|
||||
/*
|
||||
* Drag in 802.11-specific commands
|
||||
*
|
||||
|
|
|
@ -29,6 +29,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|||
*
|
||||
*/
|
||||
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
|
||||
/*
|
||||
* Provide UNDI loader if PXE stack is requested
|
||||
*
|
||||
|
|
|
@ -29,6 +29,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|||
*
|
||||
*/
|
||||
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
|
||||
/*
|
||||
* Drag in routing management for relevant protocols
|
||||
*
|
||||
|
|
|
@ -29,6 +29,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|||
*
|
||||
*/
|
||||
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
|
||||
/*
|
||||
* Drag in USB controllers
|
||||
*/
|
||||
|
|
|
@ -1765,5 +1765,8 @@ int x509_validate_chain ( struct x509_chain *chain, time_t time,
|
|||
return -EACCES_USELESS;
|
||||
}
|
||||
|
||||
/* Drag in objects via x509_validate() */
|
||||
REQUIRING_SYMBOL ( x509_validate );
|
||||
|
||||
/* Drag in certificate store */
|
||||
REQUIRE_OBJECT ( certstore );
|
||||
|
|
|
@ -1903,6 +1903,9 @@ struct usb_port * usb_root_hub_port ( struct usb_device *usb ) {
|
|||
return usb->port;
|
||||
}
|
||||
|
||||
/* Drag in objects via register_usb_bus() */
|
||||
REQUIRING_SYMBOL ( register_usb_bus );
|
||||
|
||||
/* Drag in USB configuration */
|
||||
REQUIRE_OBJECT ( config_usb );
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ FILE_LICENCE(GPL2_OR_LATER);
|
|||
|
||||
#include <ipxe/pci.h>
|
||||
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
REQUIRE_OBJECT(rtl818x);
|
||||
REQUIRE_OBJECT(rtl8180_grf5101);
|
||||
REQUIRE_OBJECT(rtl8180_max2820);
|
||||
|
|
|
@ -4,6 +4,7 @@ FILE_LICENCE(GPL2_OR_LATER);
|
|||
|
||||
#include <ipxe/pci.h>
|
||||
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
REQUIRE_OBJECT(rtl818x);
|
||||
REQUIRE_OBJECT(rtl8185_rtl8225);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ FILE_LICENCE(GPL2_OR_LATER);
|
|||
|
||||
#include <ipxe/pci.h>
|
||||
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
REQUIRE_OBJECT(vxge_main);
|
||||
|
||||
/** vxge PCI IDs for util/parserom.pl which are put into bin/NIC */
|
||||
|
|
|
@ -173,6 +173,9 @@ struct command image_trust_commands[] __command = {
|
|||
},
|
||||
};
|
||||
|
||||
/* Drag in objects via command list */
|
||||
REQUIRING_SYMBOL ( image_trust_commands );
|
||||
|
||||
/* Drag in objects typically required for signature verification */
|
||||
REQUIRE_OBJECT ( rsa );
|
||||
REQUIRE_OBJECT ( md5 );
|
||||
|
|
|
@ -57,54 +57,101 @@
|
|||
* @{
|
||||
*/
|
||||
|
||||
/** Provide a symbol within this object file */
|
||||
#ifdef ASSEMBLY
|
||||
#define PROVIDE_SYMBOL( _sym ) \
|
||||
.section ".provided", "a", @nobits ; \
|
||||
.hidden _sym ; \
|
||||
.globl _sym ; \
|
||||
_sym: ; \
|
||||
.previous
|
||||
#else /* ASSEMBLY */
|
||||
#define PROVIDE_SYMBOL( _sym ) \
|
||||
char _sym[0] \
|
||||
__attribute__ (( section ( ".provided" ) ))
|
||||
#endif /* ASSEMBLY */
|
||||
|
||||
/** Require a symbol within this object file
|
||||
/**
|
||||
* Provide a symbol within this object file
|
||||
*
|
||||
* The symbol is referenced by a relocation in a discarded section, so
|
||||
* if it is not available at link time the link will fail.
|
||||
* @v symbol Symbol name
|
||||
*/
|
||||
#ifdef ASSEMBLY
|
||||
#define REQUIRE_SYMBOL( _sym ) \
|
||||
.section ".discard", "a", @progbits ; \
|
||||
.extern _sym ; \
|
||||
.long _sym ; \
|
||||
#define PROVIDE_SYMBOL( symbol ) \
|
||||
.section ".provided", "a", @nobits ; \
|
||||
.hidden symbol ; \
|
||||
.globl symbol ; \
|
||||
symbol: ; \
|
||||
.previous
|
||||
#else /* ASSEMBLY */
|
||||
#define REQUIRE_SYMBOL( _sym ) \
|
||||
extern char _sym; \
|
||||
static char * _C2 ( _C2 ( __require_, _sym ), _C2 ( _, __LINE__ ) ) \
|
||||
__attribute__ (( section ( ".discard" ), used )) \
|
||||
= &_sym
|
||||
#else
|
||||
#define PROVIDE_SYMBOL( symbol ) \
|
||||
char symbol[0] \
|
||||
__attribute__ (( section ( ".provided" ) ))
|
||||
#endif
|
||||
|
||||
/** Request that a symbol be available at runtime
|
||||
/**
|
||||
* Request a symbol
|
||||
*
|
||||
* The requested symbol is entered as undefined into the symbol table
|
||||
* for this object, so the linker will pull in other object files as
|
||||
* necessary to satisfy the reference. However, the undefined symbol
|
||||
* is not referenced in any relocations, so the link can still succeed
|
||||
* if no file contains it.
|
||||
* @v symbol Symbol name
|
||||
*
|
||||
* Request a symbol to be included within the link. If the symbol
|
||||
* cannot be found, the link will succeed anyway.
|
||||
*/
|
||||
#ifdef ASSEMBLY
|
||||
#define REQUEST_SYMBOL( _sym ) \
|
||||
.equ __need_ ## _sym, _sym
|
||||
#else /* ASSEMBLY */
|
||||
#define REQUEST_SYMBOL( _sym ) \
|
||||
__asm__ ( ".equ\t__need_" #_sym ", " #_sym )
|
||||
#endif /* ASSEMBLY */
|
||||
#define REQUEST_SYMBOL( symbol ) \
|
||||
.equ __request_ ## symbol, symbol
|
||||
#else
|
||||
#define REQUEST_SYMBOL( symbol ) \
|
||||
__asm__ ( ".equ __request_" #symbol ", " #symbol )
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Require a symbol
|
||||
*
|
||||
* @v symbol Symbol name
|
||||
*
|
||||
* Require a symbol to be included within the link. If the symbol
|
||||
* cannot be found, the link will fail.
|
||||
*
|
||||
* To use this macro within a file, you must also specify the file's
|
||||
* "requiring symbol" using the REQUIRING_SYMBOL() or
|
||||
* PROVIDE_REQUIRING_SYMBOL() macros.
|
||||
*/
|
||||
#ifdef ASSEMBLY
|
||||
#define REQUIRE_SYMBOL( symbol ) \
|
||||
.reloc __requiring_symbol__, RELOC_TYPE_NONE, symbol
|
||||
#else
|
||||
#define REQUIRE_SYMBOL( symbol ) \
|
||||
__asm__ ( ".reloc __requiring_symbol__, " \
|
||||
_S2 ( RELOC_TYPE_NONE ) ", " #symbol )
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Specify the file's requiring symbol
|
||||
*
|
||||
* @v symbol Symbol name
|
||||
*
|
||||
* REQUIRE_SYMBOL() works by defining a dummy relocation record
|
||||
* against a nominated "requiring symbol". The presence of the
|
||||
* nominated requiring symbol will drag in all of the symbols
|
||||
* specified using REQUIRE_SYMBOL().
|
||||
*/
|
||||
#ifdef ASSEMBLY
|
||||
#define REQUIRING_SYMBOL( symbol ) \
|
||||
.equ __requiring_symbol__, symbol
|
||||
#else
|
||||
#define REQUIRING_SYMBOL( symbol ) \
|
||||
__asm__ ( ".equ __requiring_symbol__, " #symbol )
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Provide a file's requiring symbol
|
||||
*
|
||||
* If the file contains no symbols that can be used as the requiring
|
||||
* symbol, you can provide a dummy one-byte-long symbol using
|
||||
* PROVIDE_REQUIRING_SYMBOL().
|
||||
*/
|
||||
#ifdef ASSEMBLY
|
||||
#define PROVIDE_REQUIRING_SYMBOL() \
|
||||
.section ".tbl.requiring_symbols", "a", @progbits ; \
|
||||
__requiring_symbol__: .byte 0 ; \
|
||||
.size __requiring_symbol__, . - __requiring_symbol__ ; \
|
||||
.previous
|
||||
#else
|
||||
#define PROVIDE_REQUIRING_SYMBOL() \
|
||||
__asm__ ( ".section \".tbl.requiring_symbols\", " \
|
||||
" \"a\", @progbits\n" \
|
||||
"__requiring_symbol__:\t.byte 0\n" \
|
||||
".size __requiring_symbol__, " \
|
||||
" . - __requiring_symbol__\n" \
|
||||
".previous" )
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
|
||||
|
@ -119,11 +166,29 @@
|
|||
/** Always provide the symbol for the current object (defined by -DOBJECT) */
|
||||
PROVIDE_SYMBOL ( OBJECT_SYMBOL );
|
||||
|
||||
/** Explicitly require another object */
|
||||
#define REQUIRE_OBJECT( _obj ) REQUIRE_SYMBOL ( obj_ ## _obj )
|
||||
/**
|
||||
* Request an object
|
||||
*
|
||||
* @v object Object name
|
||||
*
|
||||
* Request an object to be included within the link. If the object
|
||||
* cannot be found, the link will succeed anyway.
|
||||
*/
|
||||
#define REQUEST_OBJECT( object ) REQUEST_SYMBOL ( obj_ ## object )
|
||||
|
||||
/** Pull in another object if it exists */
|
||||
#define REQUEST_OBJECT( _obj ) REQUEST_SYMBOL ( obj_ ## _obj )
|
||||
/**
|
||||
* Require an object
|
||||
*
|
||||
* @v object Object name
|
||||
*
|
||||
* Require an object to be included within the link. If the object
|
||||
* cannot be found, the link will fail.
|
||||
*
|
||||
* To use this macro within a file, you must also specify the file's
|
||||
* "requiring symbol" using the REQUIRING_SYMBOL() or
|
||||
* PROVIDE_REQUIRING_SYMBOL() macros.
|
||||
*/
|
||||
#define REQUIRE_OBJECT( object ) REQUIRE_SYMBOL ( obj_ ## object )
|
||||
|
||||
/** @} */
|
||||
|
||||
|
@ -685,8 +750,8 @@ int __debug_disable;
|
|||
|
||||
/** @} */
|
||||
|
||||
/* This file itself is under GPLv2-or-later */
|
||||
FILE_LICENCE ( GPL2_OR_LATER );
|
||||
/* This file itself is under GPLv2+/UBDL */
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
#include <bits/compiler.h>
|
||||
|
||||
|
|
|
@ -276,6 +276,7 @@ static inline void * legacy_isa_get_drvdata ( void *hwdev ) {
|
|||
_name ## _isa_legacy_remove ( struct isa_device *isa ) { \
|
||||
return legacy_remove ( isa, legacy_isa_get_drvdata, \
|
||||
_name ## _disable ); \
|
||||
}
|
||||
} \
|
||||
PROVIDE_REQUIRING_SYMBOL()
|
||||
|
||||
#endif /* NIC_H */
|
||||
|
|
|
@ -2827,5 +2827,8 @@ struct errortab common_wireless_errors[] __errortab = {
|
|||
__einfo_errortab ( EINFO_ECONNREFUSED_AUTH_ALGO_UNSUPP ),
|
||||
};
|
||||
|
||||
/* Drag in objects via net80211_ll_protocol */
|
||||
REQUIRING_SYMBOL ( net80211_ll_protocol );
|
||||
|
||||
/* Drag in 802.11 configuration */
|
||||
REQUIRE_OBJECT ( config_net80211 );
|
||||
|
|
|
@ -912,4 +912,5 @@ struct eapol_handler eapol_key_handler __eapol_handler = {
|
|||
};
|
||||
|
||||
/* WPA always needs EAPOL in order to be useful */
|
||||
REQUIRING_SYMBOL ( eapol_key_handler );
|
||||
REQUIRE_OBJECT ( eapol );
|
||||
|
|
|
@ -239,6 +239,9 @@ struct net_device * alloc_etherdev ( size_t priv_size ) {
|
|||
return netdev;
|
||||
}
|
||||
|
||||
/* Drag in objects via ethernet_protocol */
|
||||
REQUIRING_SYMBOL ( ethernet_protocol );
|
||||
|
||||
/* Drag in Ethernet configuration */
|
||||
REQUIRE_OBJECT ( config_ethernet );
|
||||
|
||||
|
|
|
@ -1940,5 +1940,8 @@ struct fc_ulp * fc_ulp_get_port_id_type ( struct fc_port *port,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* Drag in objects via fc_ports */
|
||||
REQUIRING_SYMBOL ( fc_ports );
|
||||
|
||||
/* Drag in Fibre Channel configuration */
|
||||
REQUIRE_OBJECT ( config_fc );
|
||||
|
|
|
@ -999,6 +999,9 @@ struct ib_device * last_opened_ibdev ( void ) {
|
|||
return ibdev;
|
||||
}
|
||||
|
||||
/* Drag in objects via register_ibdev() */
|
||||
REQUIRING_SYMBOL ( register_ibdev );
|
||||
|
||||
/* Drag in Infiniband configuration */
|
||||
REQUIRE_OBJECT ( config_infiniband );
|
||||
|
||||
|
|
|
@ -841,5 +841,8 @@ struct settings_applicator ipv4_settings_applicator __settings_applicator = {
|
|||
.apply = ipv4_create_routes,
|
||||
};
|
||||
|
||||
/* Drag in objects via ipv4_protocol */
|
||||
REQUIRING_SYMBOL ( ipv4_protocol );
|
||||
|
||||
/* Drag in ICMPv4 */
|
||||
REQUIRE_OBJECT ( icmpv4 );
|
||||
|
|
|
@ -1104,6 +1104,9 @@ struct net_driver ipv6_driver __net_driver = {
|
|||
.remove = ipv6_remove,
|
||||
};
|
||||
|
||||
/* Drag in objects via ipv6_protocol */
|
||||
REQUIRING_SYMBOL ( ipv6_protocol );
|
||||
|
||||
/* Drag in ICMPv6 */
|
||||
REQUIRE_OBJECT ( icmpv6 );
|
||||
|
||||
|
|
|
@ -1474,6 +1474,7 @@ struct self_test cms_test __self_test = {
|
|||
};
|
||||
|
||||
/* Drag in algorithms required for tests */
|
||||
REQUIRING_SYMBOL ( cms_test );
|
||||
REQUIRE_OBJECT ( rsa );
|
||||
REQUIRE_OBJECT ( md5 );
|
||||
REQUIRE_OBJECT ( sha1 );
|
||||
|
|
|
@ -1861,5 +1861,6 @@ struct self_test ocsp_test __self_test = {
|
|||
};
|
||||
|
||||
/* Drag in algorithms required for tests */
|
||||
REQUIRING_SYMBOL ( ocsp_test );
|
||||
REQUIRE_OBJECT ( rsa );
|
||||
REQUIRE_OBJECT ( sha1 );
|
||||
|
|
|
@ -30,6 +30,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|||
*/
|
||||
|
||||
/* Drag in all applicable self-tests */
|
||||
PROVIDE_REQUIRING_SYMBOL();
|
||||
REQUIRE_OBJECT ( memset_test );
|
||||
REQUIRE_OBJECT ( memcpy_test );
|
||||
REQUIRE_OBJECT ( string_test );
|
||||
|
|
|
@ -1109,6 +1109,7 @@ struct self_test x509_test __self_test = {
|
|||
};
|
||||
|
||||
/* Drag in algorithms required for tests */
|
||||
REQUIRING_SYMBOL ( x509_test );
|
||||
REQUIRE_OBJECT ( rsa );
|
||||
REQUIRE_OBJECT ( sha1 );
|
||||
REQUIRE_OBJECT ( sha256 );
|
||||
|
|
|
@ -48,4 +48,5 @@ void route ( void ) {
|
|||
}
|
||||
|
||||
/* Drag in routing management configuration */
|
||||
REQUIRING_SYMBOL ( route );
|
||||
REQUIRE_OBJECT ( config_route );
|
||||
|
|
|
@ -478,6 +478,9 @@ static void process_reloc ( bfd *bfd __attribute__ (( unused )),
|
|||
/* Skip absolute symbols; the symbol value won't
|
||||
* change when the object is loaded.
|
||||
*/
|
||||
} else if ( ( strcmp ( howto->name, "R_386_NONE" ) == 0 ) ||
|
||||
( strcmp ( howto->name, "R_X86_64_NONE" ) == 0 ) ) {
|
||||
/* Ignore dummy relocations used by REQUIRE_SYMBOL() */
|
||||
} else if ( strcmp ( howto->name, "R_X86_64_64" ) == 0 ) {
|
||||
/* Generate an 8-byte PE relocation */
|
||||
generate_pe_reloc ( pe_reltab, offset, 8 );
|
||||
|
|
Loading…
Reference in New Issue