From 2fcffe26e815b7125a357c83b59617ab93c16b41 Mon Sep 17 00:00:00 2001 From: Guo Ren Date: Sun, 15 Oct 2017 20:59:34 +0800 Subject: csky: port to uclibc-ng Follow the steps to build c-sky uclibc linux system: 1. git clone https://github.com/c-sky/buildroot.git 2. cd buildroot 3. make qemu_csky_ck810_uclibc_defconfig 4. make Follow the buildroot/board/qemu/csky/readme.txt to run. This buildroot toolchain is pre-build, But you can rebuild the c-sky uclibc-ng alone and install it to the buildroot sysroot manually. We'll try our best to improve the uclibc-ng continuously. Signed-off-by: Guo Ren --- ldso/ldso/csky/dl-startup.h | 115 +++++++++++++++++ ldso/ldso/csky/dl-syscalls.h | 1 + ldso/ldso/csky/dl-sysdep.h | 89 +++++++++++++ ldso/ldso/csky/elfinterp.c | 297 +++++++++++++++++++++++++++++++++++++++++++ ldso/ldso/csky/read_tp.S | 17 +++ ldso/ldso/csky/resolve.S | 72 +++++++++++ 6 files changed, 591 insertions(+) create mode 100644 ldso/ldso/csky/dl-startup.h create mode 100644 ldso/ldso/csky/dl-syscalls.h create mode 100644 ldso/ldso/csky/dl-sysdep.h create mode 100644 ldso/ldso/csky/elfinterp.c create mode 100644 ldso/ldso/csky/read_tp.S create mode 100644 ldso/ldso/csky/resolve.S (limited to 'ldso/ldso') diff --git a/ldso/ldso/csky/dl-startup.h b/ldso/ldso/csky/dl-startup.h new file mode 100644 index 000000000..0a74ab69f --- /dev/null +++ b/ldso/ldso/csky/dl-startup.h @@ -0,0 +1,115 @@ +#ifdef __CSKYABIV2__ + +__asm__ ( + " .text\n\t" + " .globl _start\n\t" + "_start:\n\t" + " mov a0, sp\n\t" + " bsr _dl_start\n\t" + " # Return from _dl_start, user entry point address in a0 \n\t" + " # the code is PIC, so get global offset table\n\t" + " grs gb,.Lgetpc1\n\t" + ".Lgetpc1:\n\t " + " lrw t0, .Lgetpc1@GOTPC\n\t" + " add gb, gb,t0\n\t" + " lrw r5, _dl_skip_args@GOT\n\t" + " ldr.w r5, (gb, r5 << 0)\n\t" + " # get the value of variable _dl_skip_args in r6\n\t" + " ldw r6, (r5, 0)\n\t" + " # get the argc in r7 \n\t" + " ldw r7, (sp, 0)\n\t" + " # adjust the argc, this may be a bug when _dl_skip_args > argc\n\t" + " rsub r6, r7\n\t" + " # adjust the stack\n\t" + " mov r7, r6\n\t" + " lsli r6, 2\n\t" + " # adjust the stack pointer,this may be a bug, " + " # because it must be 8 bytes align" + " addu sp, r6\n\t" + " stw r7, (sp, 0)\n\t" + " lrw r7, _dl_fini@GOTOFF\n\t" + " addu r7, gb\n\t" + " jmp a0" +); +#else +__asm__ ( + " .text\n\t" + " .globl _start\n\t" + "_start:\n\t" + " mov r2, r0\n\t" +# if defined(__ck810__) + " bsr _dl_start\n\t" +#else + " # the code is PIC, so get global offset table\n\t" + " bsr .Lgetpc0\n\t" + ".Lgetpc0:\n\t " + " lrw r14, .Lgetpc0@GOTPC\n\t" + " add r14, r15\n\t" + " lrw r4, _dl_start@GOTOFF\n\t" + " add r4, r14\n\t" + " jsr r4\n\t" +#endif + " # Return from _dl_start, user entry point address in r2 \n\t" + " # the code is PIC, so get global offset table\n\t" + " bsr .Lgetpc1\n\t" + ".Lgetpc1:\n\t " + " lrw r3, .Lgetpc1@GOTPC\n\t" + " add r3, r15\n\t" +# if defined(__ck810__) + " ldw r5, (r3, _dl_skip_args@GOT)\n\t" +#else + " lrw r4, _dl_skip_args@GOT\n\t" + " add r4, r3\n\t" + " ldw r5, (r4, 0)\n\t" +#endif + " # get the value of variable _dl_skip_args in r6\n\t" + " ldw r6, (r5, 0)\n\t" + " # get the argc in r7 \n\t" + " ldw r7, (r0, 0)\n\t" + " # adjust the argc, this may be a bug when _dl_skip_args > argc\n\t" + " rsub r6, r7\n\t" + " # adjust the stack\n\t" + " mov r7, r6\n\t" + " lsli r6, 2\n\t" + " # adjust the stack pointer,this may be a bug, " + " # because it must be 8 bytes align" + " addu r0, r6\n\t" + " stw r7, (r0, 0)\n\t" + " lrw r7, _dl_fini@GOTOFF\n\t" + " addu r7, r3\n\t" + " jmp r2" +); + +#endif + +/* Get a pointer to the argv array. */ +#define GET_ARGV(ARGVP, ARGS) ARGVP = (((unsigned long*)ARGS)+1) + +/* Function calls are not safe until the GOT relocations have been done. */ +#define NO_FUNCS_BEFORE_BOOTSTRAP +/* Handle relocation of the symbols in the dynamic loader. */ +static __always_inline +void PERFORM_BOOTSTRAP_RELOC(ELF_RELOC *rpnt, unsigned long *reloc_addr, + unsigned long symbol_addr, unsigned long load_addr, attribute_unused Elf32_Sym *symtab) +{ + switch (ELF32_R_TYPE(rpnt->r_info)) + { + case R_CKCORE_RELATIVE: + *reloc_addr = load_addr + rpnt->r_addend; + break; + case R_CKCORE_GLOB_DAT: + case R_CKCORE_JUMP_SLOT: + *reloc_addr = symbol_addr; + break; + case R_CKCORE_ADDR32: + *reloc_addr = symbol_addr + rpnt->r_addend; + break; + case R_CKCORE_NONE: + break; + + default: + _dl_exit(1); + } +} + + diff --git a/ldso/ldso/csky/dl-syscalls.h b/ldso/ldso/csky/dl-syscalls.h new file mode 100644 index 000000000..f40c4fd31 --- /dev/null +++ b/ldso/ldso/csky/dl-syscalls.h @@ -0,0 +1 @@ +/* stub for arch-specific syscall issues */ diff --git a/ldso/ldso/csky/dl-sysdep.h b/ldso/ldso/csky/dl-sysdep.h new file mode 100644 index 000000000..c78dd81bc --- /dev/null +++ b/ldso/ldso/csky/dl-sysdep.h @@ -0,0 +1,89 @@ +/* Define this if the system uses RELOCA. */ +#define ELF_USES_RELOCA + +#include +/* Initialization sequence for the GOT. */ +#define INIT_GOT(GOT_BASE,MODULE) \ +do { \ + GOT_BASE[2] = (unsigned long) _dl_linux_resolve; \ + GOT_BASE[1] = (unsigned long) MODULE; \ +} while(0) + +/* Here we define the magic numbers that this dynamic loader should accept */ +#define MAGIC1 EM_MCORE +#undef MAGIC2 + +/* Used for error messages */ +#define ELF_TARGET "csky" + +struct elf_resolve; +extern unsigned long _dl_linux_resolver(struct elf_resolve * tpnt, int reloc_entry); + +/* 65536 bytes alignment */ +#define PAGE_ALIGN 0xfffff000 /* need modify */ +#define ADDR_ALIGN 0xfff +#define OFFS_ALIGN 0x7ffff000 + +/* ELF_RTYPE_CLASS_PLT iff TYPE describes relocation of a PLT entry or + TLS variable, so undefined references should not be allowed to + define the value. + ELF_RTYPE_CLASS_NOCOPY iff TYPE should not be allowed to resolve to one + of the main executable's symbols, as for a COPY reloc. */ +#define elf_machine_type_class(type) \ + ((((type) == R_CKCORE_JUMP_SLOT || (type) == R_CKCORE_TLS_DTPMOD32 \ + || (type) == R_CKCORE_TLS_DTPOFF32 || (type) == R_CKCORE_TLS_TPOFF32) \ + * ELF_RTYPE_CLASS_PLT) \ + | (((type) == R_CKCORE_COPY) * ELF_RTYPE_CLASS_COPY)) + +/* Return the link-time address of _DYNAMIC. Conveniently, this is the + first element of the GOT. This must be inlined in a function which + uses global data. */ +static __inline__ Elf32_Addr elf_machine_dynamic (void) attribute_unused; +static __inline__ Elf32_Addr +elf_machine_dynamic (void) +{ + register Elf32_Addr *got __asm__ ("gb"); /* need modify */ + return *got; +} + +/* this funtion will be called only when the auxvt[AT_BASE].a_un.a_val == 0 + so it normal not be called, we should define a default address of the interprrter load */ +static __inline__ Elf32_Addr elf_machine_load_address (void) attribute_unused; +static __inline__ Elf32_Addr +elf_machine_load_address (void) +{ +#ifdef __CSKYABIV2__ + extern Elf32_Addr internal_function __dl_start (void *) __asm__ ("_dl_start"); + Elf32_Addr got_addr = (Elf32_Addr) &__dl_start; + Elf32_Addr pcrel_addr; + __asm__ ("grs %0,_dl_start\n" : "=r" (pcrel_addr)); +#else + extern Elf32_Addr internal_function __start_flag (void *) __asm__ ("start_flag"); + Elf32_Addr got_addr = (Elf32_Addr) &__start_flag; + Elf32_Addr pcrel_addr; + __asm__ ("subi sp,8\n" \ + "stw lr,(sp,0)\n" \ + "bsr start_flag\n" \ + "start_flag:" \ + "mov %0, lr\n" \ + "ldw lr,(sp,0)\n" \ + "addi sp,8\n" \ + : "=r" (pcrel_addr)); +#endif + return pcrel_addr - got_addr; + +} + +/* some relocation information are machine special */ +static __inline__ void +elf_machine_relative (Elf32_Addr load_off, const Elf32_Addr rel_addr, + Elf32_Word relative_count) +{ + Elf32_Rela *rpnt = (void *) rel_addr; + --rpnt; + do { + Elf32_Addr *reloc_addr = (void *) (load_off + (++rpnt)->r_offset); + *reloc_addr = load_off + rpnt->r_addend; + } while (--relative_count); /* maybe need modify */ +} + diff --git a/ldso/ldso/csky/elfinterp.c b/ldso/ldso/csky/elfinterp.c new file mode 100644 index 000000000..1469c28f1 --- /dev/null +++ b/ldso/ldso/csky/elfinterp.c @@ -0,0 +1,297 @@ +#include "ldso.h" + +unsigned long +_dl_linux_resolver(struct elf_resolve *tpnt, int reloc_entry) +{ + ELF_RELOC *this_reloc; + int symtab_index; + //char *rel_tab; + Elf32_Sym *sym_tab; + char *str_tab; + char *sym_name; + char *sym_addr; + char **reloc_addr; + + this_reloc = (ELF_RELOC *)tpnt->dynamic_info[DT_JMPREL]; + this_reloc += reloc_entry; + //this_reloc = (ELF_RELOC *)(intptr_t)(rel_tab + reloc_entry); + symtab_index = ELF32_R_SYM(this_reloc->r_info); + + sym_tab = (Elf32_Sym *)(intptr_t)tpnt->dynamic_info[DT_SYMTAB]; + str_tab = (char *)tpnt->dynamic_info[DT_STRTAB]; + sym_name = str_tab + sym_tab[symtab_index].st_name; + + reloc_addr = (char **)((unsigned long)this_reloc->r_offset + + (unsigned long)tpnt->loadaddr); + + sym_addr = _dl_find_hash(sym_name, &_dl_loaded_modules->symbol_scope, tpnt, ELF_RTYPE_CLASS_PLT, NULL); + + if (unlikely(!sym_addr)) { + _dl_dprintf(2, "%s: 1can't resolve symbol '%s' in lib '%s'.\n", _dl_progname, sym_name, tpnt->libname); + _dl_exit(1); + } + + *reloc_addr = sym_addr; + + return (unsigned long)sym_addr; +} + +static int +_dl_parse(struct elf_resolve *tpnt, struct r_scope_elem*scope, + unsigned long rel_addr, unsigned long rel_size, + int (*reloc_fnc)(struct elf_resolve *tpnt, struct r_scope_elem*scope, + ELF_RELOC *rpnt, Elf32_Sym *symtab, char *strtab)) +{ + unsigned int i; + char *strtab; + Elf32_Sym *symtab; + ELF_RELOC *rpnt; + int symtab_index; + + /* Parse the relocation information. */ + rpnt = (ELF_RELOC *)(intptr_t)rel_addr; + rel_size /= sizeof(ELF_RELOC); + symtab = (Elf32_Sym *)(intptr_t)tpnt->dynamic_info[DT_SYMTAB]; + strtab = (char *)tpnt->dynamic_info[DT_STRTAB]; + + for (i = 0; i < rel_size; i++, rpnt++) { + int res; + + symtab_index = ELF32_R_SYM(rpnt->r_info); + + debug_sym(symtab, strtab, symtab_index); + debug_reloc(symtab, strtab, rpnt); + + res = reloc_fnc(tpnt, scope, rpnt, symtab, strtab); + if (res == 0) + continue; + _dl_dprintf(2, "\n%s: ", _dl_progname); + + if (symtab_index) + _dl_dprintf(2, "symbol '%s': ", + strtab + symtab[symtab_index].st_name); + if (unlikely(res < 0)) { + int reloc_type = ELF32_R_TYPE(rpnt->r_info); + +#if defined (__SUPPORT_LD_DEBUG__) + _dl_dprintf(2, "2can't handle reloc type '%s' in lib '%s'\n", + _dl_reltypes(reloc_type), tpnt->libname); +#else + _dl_dprintf(2, "3can't handle reloc type %x in lib '%s'\n", + reloc_type, tpnt->libname); +#endif + return res; + } else if (unlikely(res > 0)) { + _dl_dprintf(2, "4can't resolve symbol in lib '%s'.\n", tpnt->libname); + return res; + } + } + + return 0; +} + +static int +_dl_do_reloc(struct elf_resolve *tpnt, struct r_scope_elem *scope, + ELF_RELOC *rpnt, Elf32_Sym *symtab, char *strtab) +{ + int reloc_type; + int symtab_index; + char *symname; + unsigned long *reloc_addr; + unsigned long symbol_addr; + struct symbol_ref sym_ref; +#if defined (__SUPPORT_LD_DEBUG__) + unsigned long old_val; +#endif +#if defined USE_TLS && USE_TLS + struct elf_resolve *tls_tpnt = NULL; +#endif +#if defined(__CSKYABIV2__) + unsigned int insn_opcode = 0x0; + unsigned short *opcode16_addr; +#endif + + reloc_addr = (unsigned long *)(intptr_t)(tpnt->loadaddr + (unsigned long)rpnt->r_offset); +#if defined(__CSKYABIV2__) + opcode16_addr = (unsigned short *)reloc_addr; +#endif + reloc_type = ELF32_R_TYPE(rpnt->r_info); + + if (reloc_type == R_CKCORE_NONE) + return 0; + + symtab_index = ELF32_R_SYM(rpnt->r_info); + symbol_addr = 0; + sym_ref.sym = &symtab[symtab_index]; + sym_ref.tpnt = NULL; + symname = strtab + symtab[symtab_index].st_name; + if (symtab_index) { + symbol_addr = (unsigned long)_dl_find_hash(symname, scope, tpnt, + elf_machine_type_class(reloc_type), &sym_ref); + /* + * We want to allow undefined references to weak symbols - this + * might have been intentional. We should not be linking local + * symbols here, so all bases should be covered. + */ + // if (unlikely(!symbol_addr && ELF32_ST_BIND(symtab[symtab_index].st_info) != STB_WEAK)) + if (!symbol_addr && (ELF_ST_TYPE(symtab[symtab_index].st_info) != STT_TLS) + && (ELF_ST_BIND(symtab[symtab_index].st_info) != STB_WEAK)) + return 1; +#if defined USE_TLS && USE_TLS + tls_tpnt = sym_ref.tpnt; +#endif + }else{ + /* + * Relocs against STN_UNDEF are usually treated as using a + * symbol value of zero, and using the module containing the + * reloc itself. + */ + symbol_addr = symtab[symtab_index].st_name; +#if defined USE_TLS && USE_TLS + tls_tpnt = tpnt; +#endif + } +#if defined (__SUPPORT_LD_DEBUG__) + old_val = *reloc_addr; +#endif + + switch (reloc_type) { /* need modify */ + case R_CKCORE_NONE: + case R_CKCORE_PCRELJSR_IMM11BY2: + break; + case R_CKCORE_ADDR32: + *reloc_addr = symbol_addr + rpnt->r_addend; + break; + case R_CKCORE_GLOB_DAT: + case R_CKCORE_JUMP_SLOT: + *reloc_addr = symbol_addr; + break; + case R_CKCORE_RELATIVE: + *reloc_addr = (unsigned long)tpnt->loadaddr + rpnt->r_addend; + break; +#if defined(__CSKYABIV2__) + case R_CKCORE_ADDR_HI16: + insn_opcode = (*opcode16_addr << 16) | (*(opcode16_addr + 1)); + insn_opcode = (insn_opcode & 0xffff0000) + | (((symbol_addr + rpnt->r_addend) >> 16) & 0xffff); + *(opcode16_addr++) = (unsigned short)(insn_opcode >> 16); + *opcode16_addr = (unsigned short)(insn_opcode & 0xffff); + break; + case R_CKCORE_ADDR_LO16: + insn_opcode = (*opcode16_addr << 16) | (*(opcode16_addr + 1)); + insn_opcode = (insn_opcode & 0xffff0000) + | ((symbol_addr + rpnt->r_addend) & 0xffff); + *(opcode16_addr++) = (unsigned short)(insn_opcode >> 16); + *opcode16_addr = (unsigned short)(insn_opcode & 0xffff); + break; + case R_CKCORE_PCREL_IMM26BY2: + { + unsigned int offset = ((symbol_addr + rpnt->r_addend - + (unsigned int)reloc_addr) >> 1); + insn_opcode = (*opcode16_addr << 16) | (*(opcode16_addr + 1)); + if (offset > 0x3ffffff){ + _dl_dprintf(2, "%s:The reloc R_CKCORE_PCREL_IMM26BY2 cannot reach the symbol '%s'.\n", _dl_progname, symname); + _dl_exit(1); + } + insn_opcode = (insn_opcode & ~0x3ffffff) | offset; + *(opcode16_addr++) = (unsigned short)(insn_opcode >> 16); + *opcode16_addr = (unsigned short)(insn_opcode & 0xffff); + break; + } + case R_CKCORE_PCREL_JSR_IMM26BY2: + break; +#endif + case R_CKCORE_COPY: + if (symbol_addr) { +#if defined (__SUPPORT_LD_DEBUG__) + if (_dl_debug_move) + _dl_dprintf(_dl_debug_file, + "\n%s move %d bytes from %x to %x", + symname, symtab[symtab_index].st_size, + symbol_addr, reloc_addr); +#endif + + _dl_memcpy((char *)reloc_addr, + (char *)symbol_addr, + symtab[symtab_index].st_size); + } + break; +#if defined USE_TLS && USE_TLS + case R_CKCORE_TLS_DTPMOD32: + *reloc_addr = tls_tpnt->l_tls_modid; + break; + case R_CKCORE_TLS_DTPOFF32: + *reloc_addr += symbol_addr; + break; + case R_CKCORE_TLS_TPOFF32: + CHECK_STATIC_TLS ((struct link_map *) tls_tpnt); + *reloc_addr += tls_tpnt->l_tls_offset + symbol_addr; + break; +#endif + default: + return -1; + } + +#if defined (__SUPPORT_LD_DEBUG__) + if (_dl_debug_reloc && _dl_debug_detail) + _dl_dprintf(_dl_debug_file, "\n\tpatched: %x ==> %x @ %x", + old_val, *reloc_addr, reloc_addr); +#endif + + return 0; +} + +static int +_dl_do_lazy_reloc(struct elf_resolve *tpnt, struct r_scope_elem *scope, + ELF_RELOC *rpnt, Elf32_Sym *symtab, char *strtab) +{ + int reloc_type; + unsigned long *reloc_addr; +#if defined (__SUPPORT_LD_DEBUG__) + unsigned long old_val; +#endif + + reloc_addr = (unsigned long *)(intptr_t)(tpnt->loadaddr + (unsigned long)rpnt->r_offset); + reloc_type = ELF32_R_TYPE(rpnt->r_info); + +#if defined (__SUPPORT_LD_DEBUG__) + old_val = *reloc_addr; +#endif + + switch (reloc_type) { + case R_CKCORE_NONE: + case R_CKCORE_PCRELJSR_IMM11BY2: + break; + case R_CKCORE_JUMP_SLOT: + *reloc_addr = (unsigned long)tpnt->loadaddr + rpnt->r_addend; + break; + default: + return -1; + } + +#if defined (__SUPPORT_LD_DEBUG__) + if (_dl_debug_reloc && _dl_debug_detail) + _dl_dprintf(_dl_debug_file, "\n\tpatched: %x ==> %x @ %x", + old_val, *reloc_addr, reloc_addr); +#endif + + return 0; +} + + +void +_dl_parse_lazy_relocation_information(struct dyn_elf *rpnt, + unsigned long rel_addr, + unsigned long rel_size) +{ + (void)_dl_parse(rpnt->dyn, NULL, rel_addr, rel_size, _dl_do_lazy_reloc); +} + +int +_dl_parse_relocation_information(struct dyn_elf *rpnt, + struct r_scope_elem *scope, + unsigned long rel_addr, + unsigned long rel_size) +{ + return _dl_parse(rpnt->dyn, scope, rel_addr, rel_size, _dl_do_reloc); +} diff --git a/ldso/ldso/csky/read_tp.S b/ldso/ldso/csky/read_tp.S new file mode 100644 index 000000000..ef8d509d5 --- /dev/null +++ b/ldso/ldso/csky/read_tp.S @@ -0,0 +1,17 @@ +#include + +#ifndef NOT_IN_libc +.global __read_tp +#else +.hidden __read_tp +#endif + +ENTRY (__read_tp) +#ifdef __CSKYABIV2__ + mov a0, tls +#else + trap 3 +#endif + rts +END (__read_tp) + diff --git a/ldso/ldso/csky/resolve.S b/ldso/ldso/csky/resolve.S new file mode 100644 index 000000000..46a436345 --- /dev/null +++ b/ldso/ldso/csky/resolve.S @@ -0,0 +1,72 @@ +/* + * This function is not called directly. It is jumped when attempting to use a + * symbol that has not yet been resolved. + * + *.plt*: + * subi r0, 32 + * stw r2, (r0, 0) + * stw r3, (r0, 4) + * lrw r3, #offset + * ldw r2, (gb, 8) + * jmp r2 + */ + +.import _dl_linux_resolver + +.text +.globl _dl_linux_resolve +.type _dl_linux_resolve,@function + +_dl_linux_resolve: +#ifdef __CSKYABIV1__ + stw r4, (r0, 8) + stw r5, (r0, 12) + stw r6, (r0, 16) + stw r7, (r0, 20) + stw r15,(r0, 24) + # load the ID of this module + ldw r2, (gb, 4) + # r2 = id, r3 = offset(do it in plt*) +#ifdef __PIC__ + # get global offset table address_ + bsr .L2 +.L2: + lrw r7, .L2@GOTPC + add r7, r15 + # get the address of function (_dl_linux_resolver) in got table + lrw r6, _dl_linux_resolver@GOT + add r6, r7 + ldw r5, (r6, 0) + jsr r5 +#else /* no __PIC__ */ + jsri _dl_linux_resolver /* need modify */ +#endif + # Return from _dl_linux_resolver, the address of function is in r2 + mov r1, r2 + # Restore the registers + ldw r2, (r0, 0) + ldw r3, (r0, 4) + ldw r4, (r0, 8) + ldw r5, (r0, 12) + ldw r6, (r0, 16) + ldw r7, (r0, 20) + ldw r15,(r0, 24) + # Restore the r0, because r0 is subtracted in PLT table + addi r0, 32 + # The address of function is in r1, call the function without saving pc + jmp r1 +#else /* __CSKYABIV1__ */ + subi sp, 20 + stm a0-a3, (sp) + stw lr, (sp, 16) + # a0 = id, a1 = offset(do it in plt*) + ldw a0, (gb, 4) + mov a1, t1 + bsr _dl_linux_resolver + mov t0, a0 + ldw lr, (sp, 16) + ldm a0-a3, (sp) + addi sp, 20 + jmp t0 + +#endif -- cgit v1.2.3