diff options
author | Mike Frysinger <vapier@gentoo.org> | 2006-12-08 22:53:40 +0000 |
---|---|---|
committer | Mike Frysinger <vapier@gentoo.org> | 2006-12-08 22:53:40 +0000 |
commit | 808694e8a330e32741b7781467610d8cec99ae6e (patch) | |
tree | a2114e7c67f1f88f5df4687d6ab7122892e68283 | |
parent | cba2c53724a6ed35f32775ec38906268c1bbd340 (diff) |
Richard Sandiford writes: add support for init/fini arrays in shared flat libraries
-rw-r--r-- | Makefile.in | 9 | ||||
-rw-r--r-- | Makerules | 28 | ||||
-rw-r--r-- | Rules.mak | 13 | ||||
-rw-r--r-- | extra/Configs/Config.in | 24 | ||||
-rw-r--r-- | libc/Makefile.in | 4 | ||||
-rw-r--r-- | libc/misc/internals/Makefile.in | 4 | ||||
-rw-r--r-- | libc/misc/internals/__uClibc_main.c | 16 | ||||
-rw-r--r-- | libc/misc/internals/shared_flat_add_library.c | 46 | ||||
-rw-r--r-- | libc/misc/internals/shared_flat_initfini.c | 53 | ||||
-rw-r--r-- | libc/misc/internals/shared_flat_lib.h | 35 | ||||
-rw-r--r-- | libc/sysdeps/linux/m68k/bsd-setjmp.S | 4 | ||||
-rw-r--r-- | libc/sysdeps/linux/m68k/clone.S | 11 | ||||
-rw-r--r-- | libc/sysdeps/linux/m68k/crt1.S | 41 | ||||
-rw-r--r-- | libc/sysdeps/linux/m68k/crti.S | 22 | ||||
-rw-r--r-- | libc/sysdeps/linux/m68k/crtn.S | 27 | ||||
-rw-r--r-- | libc/sysdeps/linux/m68k/m68k_pic.S | 64 | ||||
-rw-r--r-- | libc/sysdeps/linux/m68k/setjmp.S | 4 |
17 files changed, 336 insertions, 69 deletions
diff --git a/Makefile.in b/Makefile.in index 8eaa7cffb..4d98c1f8f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -246,6 +246,15 @@ ifeq ($(DOPIC),y) fi endif endif +ifeq ($(HAVE_SHARED_FLAT),y) + for file in lib/lib*.gdb; do \ + if test -f $$file; then \ + $(INSTALL) -m 755 $$file $(PREFIX)$(DEVEL_PREFIX)lib; \ + $(INSTALL) -m 755 `echo $$file | sed 's/\.gdb$$//'` \ + $(PREFIX)$(DEVEL_PREFIX)lib; \ + fi; \ + done +endif # Installs run-time libraries install_runtime: @@ -11,6 +11,9 @@ libs: $(lib-so-y) $(lib-a-y) $(lib-so-y): $(interp) else .LIBPATTERNS: "lib%.a" +ifeq ($(HAVE_SHARED_FLAT),y) +libs: $(lib-gdb-y) +endif libs: $(lib-a-y) endif objs: all_objs @@ -131,6 +134,29 @@ define link.so $(Q)$(LN) -sf $(1) $@ endef +# CRT files needed by link-flat.so +LINK_FLAT_CRTS := $(top_builddir)lib/Scrt1.o $(top_builddir)lib/crti.o \ + $(top_builddir)lib/crtn.o + +# Create a shared flat library from the archive named by the first dependency. +# $@ names the shared library's .gdb file, not the flat file itself. +# (This is because the .gdb suffix makes the ELF file more distinctive +# than the suffixless flat file.) +# +# Argument 1 is the shared library file -- i.e. $(@:.gdb=) -- and argument 2 +# is the shared library identifier. If it wasn't for $(disp_ld), we could +# avoid passing $(@:.gdb=) as an argument and use $(@:.gdb=) instead of $(1). +define link-flat.so + $(Q)$(INSTALL) -d $(dir $@) + $(Q)$(RM) $(1) $@ + @$(disp_ld) + $(Q)$(LD) $(LDFLAGS-$(notdir $@)) -o $(1) \ + -elf2flt -shared-lib-id $(2) $(top_builddir)lib/Scrt1.o \ + $(top_builddir)/lib/crti.o --whole-archive $(firstword $^) \ + --no-whole-archive $(LIBS-$(notdir $@)) $(LIBGCC) \ + $(top_builddir)/lib/crtn.o +endef + define linkm.so $(Q)$(INSTALL) -d $(dir $@) $(Q)$(RM) $@ $@.$(2) $(dir $@)$(1) @@ -181,7 +207,7 @@ $(headers_dep): CRT := crt1 -ifeq ($(HAVE_SHARED),y) +ifeq ($(HAVE_SHARED)$(HAVE_SHARED_FLAT),y) CRTS=$(top_builddir)lib/$(CRT).o $(top_builddir)lib/S$(CRT).o else CRTS=$(top_builddir)lib/$(CRT).o @@ -506,16 +506,3 @@ ifeq ($(UCLIBC_CTOR_DTOR),y) SHARED_START_FILES:=$(top_builddir)lib/crti.o $(LIBGCC_DIR)crtbeginS.o SHARED_END_FILES:=$(LIBGCC_DIR)crtendS.o $(top_builddir)lib/crtn.o endif - -######################################## -# -# uClinux shared lib support -# - -ifeq ($(CONFIG_BINFMT_SHARED_FLAT),y) - # For the shared version of this, we specify no stack and its library ID - FLTFLAGS += -s 0 - LIBID=1 - export LIBID FLTFLAGS - SHARED_TARGET = lib/libc -endif diff --git a/extra/Configs/Config.in b/extra/Configs/Config.in index b3d9250f0..9999b370e 100644 --- a/extra/Configs/Config.in +++ b/extra/Configs/Config.in @@ -196,6 +196,30 @@ config DOPIC If you wish to build all of uClibc as PIC objects, then answer Y here. If you are unsure, then you should answer N. +config HAVE_SHARED_FLAT + bool "Generate a flat-format shared library" + default n + depends on !ARCH_USE_MMU + help + Answer Y here if you are using uClinux and wish to build uClibc + as a flat-format shared library. + +config SHARED_FLAT_ID + int "Shared library id" + default 1 + depends on HAVE_SHARED_FLAT + help + When using flat shared libraries, every library has a unique + system-wide identifier. Identifier 0 is reserved for + executables and true shared libraries have identifiers + starting at 1. The maximum shared library identifier is + determined by the kernel and is usually 3. Shared library + N must be available on the target system as "/lib/libN.so". + + When a shared C library is used, it usually has identifier 1, + but you can use this option to select a different identifier + if you need to. + config HAVE_NO_SHARED bool default n diff --git a/libc/Makefile.in b/libc/Makefile.in index 6303a864c..0387bfafd 100644 --- a/libc/Makefile.in +++ b/libc/Makefile.in @@ -50,6 +50,7 @@ libc-multi-y = $(filter-out $(libc-nomulti-y:.o=.c),$(all_sources)) endif lib-a-y += $(top_builddir)lib/libc.a +lib-gdb-y += $(top_builddir)lib/libc.gdb lib-so-y += $(libc) objclean-y += libc_clean @@ -89,5 +90,8 @@ $(top_builddir)lib/libc.a: $(libc-a-y) | $(crt-y) $(do_strip) $(do_ar) +$(top_builddir)lib/libc.gdb: $(top_builddir)lib/libc.a $(LINK_FLAT_CRTS) + $(call link-flat.so,$(@:.gdb=),$(SHARED_FLAT_ID)) + libc_clean: $(RM) $(libc_OUT)/*.{o,os,oS,a} diff --git a/libc/misc/internals/Makefile.in b/libc/misc/internals/Makefile.in index a9638125d..af97b39af 100644 --- a/libc/misc/internals/Makefile.in +++ b/libc/misc/internals/Makefile.in @@ -18,7 +18,9 @@ MISC_INTERNALS_OBJ := $(patsubst %.c,$(MISC_INTERNALS_OUT)/%.o,$(CSRC)) libc-y += $(MISC_INTERNALS_OBJ) libc-shared-y += $(MISC_INTERNALS_OUT)/__uClibc_main.oS libc-static-y += $(MISC_INTERNALS_OUT)/__uClibc_main.o - +libc-static-$(HAVE_SHARED_FLAT) += \ + $(MISC_INTERNALS_OUT)/shared_flat_initfini.o \ + $(MISC_INTERNALS_OUT)/shared_flat_add_library.o libc-nomulti-y += $(MISC_INTERNALS_OUT)/__uClibc_main.o objclean-y += misc_internals_objclean diff --git a/libc/misc/internals/__uClibc_main.c b/libc/misc/internals/__uClibc_main.c index 7578a97fb..a2fbf6c89 100644 --- a/libc/misc/internals/__uClibc_main.c +++ b/libc/misc/internals/__uClibc_main.c @@ -88,7 +88,9 @@ extern void weak_function _locale_init(void) attribute_hidden; extern void weak_function __pthread_initialize_minimal(void); #endif -#ifdef __UCLIBC_CTOR_DTOR__ +/* If __HAVE_SHARED_FLAT__, all array initialisation and finalisation + * is handled by the routines passed to __uClibc_main(). */ +#if defined (__UCLIBC_CTOR_DTOR__) && !defined (__HAVE_SHARED_FLAT__) extern void _dl_app_init_array(void); extern void _dl_app_fini_array(void); # ifndef SHARED @@ -247,9 +249,11 @@ libc_hidden_proto(__uClibc_fini) void __uClibc_fini(void) { #ifdef __UCLIBC_CTOR_DTOR__ + /* If __HAVE_SHARED_FLAT__, all array finalisation is handled + * by __app_fini. */ # ifdef SHARED _dl_app_fini_array(); -# else +# elif !defined (__HAVE_SHARED_FLAT__) size_t i = __fini_array_end - __fini_array_start; while (i-- > 0) (*__fini_array_start [i]) (); @@ -348,7 +352,9 @@ void __uClibc_main(int (*main)(int, char **, char **), int argc, /* Arrange for the application's dtors to run before we exit. */ __app_fini = app_fini; -# ifndef SHARED + /* If __HAVE_SHARED_FLAT__, all array initialisation is handled + * by __app_init. */ +# if !defined (SHARED) && !defined (__HAVE_SHARED_FLAT__) /* For dynamically linked executables the preinit array is executed by the dynamic linker (before initializing any shared object). For static executables, preinit happens rights before init. */ @@ -363,9 +369,11 @@ void __uClibc_main(int (*main)(int, char **, char **), int argc, if (app_init!=NULL) { app_init(); } + /* If __HAVE_SHARED_FLAT__, all array initialisation is handled + * by __app_init. */ # ifdef SHARED _dl_app_init_array(); -# else +# elif !defined (__HAVE_SHARED_FLAT__) { const size_t size = __init_array_end - __init_array_start; size_t i; diff --git a/libc/misc/internals/shared_flat_add_library.c b/libc/misc/internals/shared_flat_add_library.c new file mode 100644 index 000000000..f03480f56 --- /dev/null +++ b/libc/misc/internals/shared_flat_add_library.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2006 CodeSourcery Inc + * + * Licensed under the LGPL v2.1, see the file COPYING.LIB in this tarball. + * + * This file defines __shared_flat_add_library. If a library has + * initialistion and finalisation code, it should use this routine + * to register itself. + */ +#include "shared_flat_lib.h" + +/* The initialisation and finalisation symbols for this library. */ +extern void _init(void) attribute_hidden weak_function; +extern void _fini(void) attribute_hidden weak_function; +extern void (*__preinit_array_start[])(void) attribute_hidden; +extern void (*__preinit_array_end[])(void) attribute_hidden; +extern void (*__init_array_start[])(void) attribute_hidden; +extern void (*__init_array_end[])(void) attribute_hidden; +extern void (*__fini_array_start[])(void) attribute_hidden; +extern void (*__fini_array_end[])(void) attribute_hidden; + +/* The shared_flat_lib structure that describes this library. */ +static struct shared_flat_lib this_lib = { + 0, + 0, + __preinit_array_start, + __preinit_array_end, + __init_array_start, + __init_array_end, + __fini_array_start, + __fini_array_end, + _init, + _fini +}; + +/* Add this_lib to the end of the global list. */ +void __shared_flat_add_library(void) attribute_hidden; +void __shared_flat_add_library(void) +{ + this_lib.prev = __last_shared_lib; + if (this_lib.prev) + this_lib.prev->next = &this_lib; + else + __first_shared_lib = &this_lib; + __last_shared_lib = &this_lib; +} diff --git a/libc/misc/internals/shared_flat_initfini.c b/libc/misc/internals/shared_flat_initfini.c new file mode 100644 index 000000000..81e53834e --- /dev/null +++ b/libc/misc/internals/shared_flat_initfini.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2006 CodeSourcery Inc + * + * Licensed under the LGPL v2.1, see the file COPYING.LIB in this tarball. + * + * This file defines the main initialisation and finalisation code for + * shared flat libraries. It in turn calls the initialisation and + * finalisation code for each registered library. + */ +#include "shared_flat_lib.h" + +/* A doubly-linked list of shared libraries. Those nearer the head + * of the list should be initialised first and finalised last. */ +struct shared_flat_lib *__first_shared_lib; +struct shared_flat_lib *__last_shared_lib; + +void __shared_flat_init(void) +{ + struct shared_flat_lib *lib; + void (**start)(void); + void (**end)(void); + + for (lib = __first_shared_lib; lib; lib = lib->next) { + end = lib->preinit_array_end; + for (start = lib->preinit_array_start; start < end; start++) + (*start)(); + } + + for (lib = __first_shared_lib; lib; lib = lib->next) { + if (lib->init) + lib->init(); + + end = lib->init_array_end; + for (start = lib->init_array_start; start < end; start++) + (*start)(); + } +} + +void __shared_flat_fini(void) +{ + struct shared_flat_lib *lib; + void (**start)(void); + void (**end)(void); + + for (lib = __last_shared_lib; lib; lib = lib->prev) { + start = lib->fini_array_start; + for (end = lib->fini_array_end; end > start;) + (*--end)(); + + if (lib->fini) + lib->fini(); + } +} diff --git a/libc/misc/internals/shared_flat_lib.h b/libc/misc/internals/shared_flat_lib.h new file mode 100644 index 000000000..e01213564 --- /dev/null +++ b/libc/misc/internals/shared_flat_lib.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2006 CodeSourcery Inc + * + * Licensed under the LGPL v2.1, see the file COPYING.LIB in this tarball. + * + * This file defines the shared_flat_lib structure and the global library + * list. The structure is used to provide something close to ELF-like + * initialisation and finalisation when using shared flat libraries. + */ +#ifndef __SHARED_FLAT_LIB__ +#define __SHARED_FLAT_LIB__ + +struct shared_flat_lib { + struct shared_flat_lib *prev; + struct shared_flat_lib *next; + /* .preinit_array is usually only supported for executables. + * However, the distinction between the executable and its + * shared libraries isn't as pronounced for flat files; a shared + * library is really just a part of an executable that can be + * shared with other executables. We therefore allow + * .preinit_array to be used in libraries too. */ + void (**preinit_array_start)(void); + void (**preinit_array_end)(void); + void (**init_array_start)(void); + void (**init_array_end)(void); + void (**fini_array_start)(void); + void (**fini_array_end)(void); + void (*init)(void); + void (*fini)(void); +}; + +extern struct shared_flat_lib *__first_shared_lib; +extern struct shared_flat_lib *__last_shared_lib; + +#endif diff --git a/libc/sysdeps/linux/m68k/bsd-setjmp.S b/libc/sysdeps/linux/m68k/bsd-setjmp.S index b83573c80..fdd7540a6 100644 --- a/libc/sysdeps/linux/m68k/bsd-setjmp.S +++ b/libc/sysdeps/linux/m68k/bsd-setjmp.S @@ -5,6 +5,7 @@ #define _ASM #define _SETJMP_H #include <bits/setjmp.h> +#include "m68k_pic.S" .globl setjmp; .type setjmp,@function @@ -18,6 +19,5 @@ setjmp: fmovemx %fp2-%fp7, %a0@(JB_FPREGS) #endif clrl %d0 - lea __sigjmp_save-.-8, %a0 - jmp 0(%pc, %a0) + JUMP __sigjmp_save,%a0 diff --git a/libc/sysdeps/linux/m68k/clone.S b/libc/sysdeps/linux/m68k/clone.S index 8ef916e91..7eddff10c 100644 --- a/libc/sysdeps/linux/m68k/clone.S +++ b/libc/sysdeps/linux/m68k/clone.S @@ -8,6 +8,7 @@ #include <features.h> #include <bits/errno.h> #include <sys/syscall.h> +#include "m68k_pic.S" /* int _clone(int (*fn)(void *arg), void *child_stack, int flags, void *arg); */ @@ -21,11 +22,11 @@ clone: movel 4(%sp), %d1 /* no NULL function pointers */ movel %d1, %a0 tstl %d1 - jbeq __syscall_error + beq.w __syscall_error_trampoline movel 8(%sp), %d1 /* no NULL stack pointers */ movel %d1, %a1 tstl %d1 - jbeq __syscall_error + beq.w __syscall_error_trampoline /* Allocate space and copy the argument onto the new stack. */ movel 16(%sp), -(%a1) @@ -50,7 +51,7 @@ clone: #endif tstl %d0 - jbmi __syscall_error + bmi.w __syscall_error_trampoline beq.w thread_start rts @@ -62,3 +63,7 @@ thread_start: movel #__NR_exit, %d0 trap #0 /*jsr exit*/ + +__syscall_error_trampoline: + JUMP __syscall_error,%a0 + diff --git a/libc/sysdeps/linux/m68k/crt1.S b/libc/sysdeps/linux/m68k/crt1.S index 9ce14e594..a5f973fe2 100644 --- a/libc/sysdeps/linux/m68k/crt1.S +++ b/libc/sysdeps/linux/m68k/crt1.S @@ -34,6 +34,10 @@ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ +#include <features.h> +#include "m68k_pic.S" + +#ifndef L_Scrt1 /* This is the canonical entry point, usually the first thing in the text segment. The SVR4/m68k ABI says that when the entry point runs, most registers' values are unspecified, except for: @@ -60,8 +64,6 @@ 8(%sp) envp */ -#include <features.h> - .text .type _init,%function .type _fini,%function @@ -76,6 +78,17 @@ _start: the outermost frame obviously. */ sub.l %fp, %fp +#if !defined __ARCH_USE_MMU__ && defined __PIC__ + /* Set up the global pointer. The GOT is at the beginning of the + data segment, whose address is in %d5. */ + move.l %d5,%a5 + .equ have_current_got, 1 +#endif + +#ifdef __HAVE_SHARED_FLAT__ + CALL __shared_flat_add_library,%a1 +#endif + /* Extract the arguments as encoded on the stack and set up the arguments for `main': argc, argv. envp will be determined later in __libc_start_main. */ @@ -100,19 +113,35 @@ _start: /* Push the address of our own entry points to `.fini' and `.init'. */ - pea _fini - pea _init +#if defined __HAVE_SHARED_FLAT__ + PEA_TEXT __shared_flat_fini,%a1 + PEA_TEXT __shared_flat_init,%a1 +#else + PEA_TEXT _fini,%a1 + PEA_TEXT _init,%a1 +#endif pea (%a0) /* Push second argument: argv. */ move.l %d0, -(%sp) /* Push first argument: argc. */ - pea main + PEA_TEXT main,%a1 /* Call the user's main function, and exit with its value. But let the libc call main. */ - jbsr __uClibc_main + CALL __uClibc_main,%a1 illegal /* Crash if somehow `exit' does return. */ +#else + .text + .globl lib_main + .hidden lib_main + .type lib_main,@function +lib_main: + move.l %d5,%a5 + JUMP __shared_flat_add_library,%a0 + + .hidden _current_shared_library_a5_offset_ +#endif /* Define a symbol for the first piece of initialized data. */ .data diff --git a/libc/sysdeps/linux/m68k/crti.S b/libc/sysdeps/linux/m68k/crti.S index ccfe99133..1b4b643b6 100644 --- a/libc/sysdeps/linux/m68k/crti.S +++ b/libc/sysdeps/linux/m68k/crti.S @@ -1,27 +1,17 @@ - .file "initfini.c" -#APP - +#include "m68k_pic.S" + .section .init -#NO_APP .align 2 .globl _init .type _init, @function _init: - link.w %a6,#0 -#APP - - .align 2 - + link.w %a6,#0 + INIT_GP .section .fini -#NO_APP .align 2 .globl _fini .type _fini, @function _fini: - link.w %a6,#0 -#APP - .align 2 - - - .ident "GCC: (GNU) 3.3.2" + link.w %a6,#0 + INIT_GP diff --git a/libc/sysdeps/linux/m68k/crtn.S b/libc/sysdeps/linux/m68k/crtn.S index d29c02dec..2a29b8726 100644 --- a/libc/sysdeps/linux/m68k/crtn.S +++ b/libc/sysdeps/linux/m68k/crtn.S @@ -1,26 +1,11 @@ - .file "initfini.c" -#APP - +#include "m68k_pic.S" + .section .init -#NO_APP - .align 2 - .globl _init - .type _init, @function -#NO_APP - unlk %a6 + FINI_GP + unlk %a6 rts - .size _init, .-_init -#APP .section .fini -#NO_APP - .align 2 - .globl _fini - .type _fini, @function -#NO_APP - unlk %a6 + FINI_GP + unlk %a6 rts - .size _fini, .-_fini -#APP - - .ident "GCC: (GNU) 3.3.2" diff --git a/libc/sysdeps/linux/m68k/m68k_pic.S b/libc/sysdeps/linux/m68k/m68k_pic.S new file mode 100644 index 000000000..e01e33b83 --- /dev/null +++ b/libc/sysdeps/linux/m68k/m68k_pic.S @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2006 CodeSourcery Inc + * + * Licensed under the LGPL v2.1, see the file COPYING.LIB in this tarball. + * + * This file defines some m68k assembly macros for handling the differences + * between PIC and non-PIC. + */ +#include <features.h> + + /* When assembling code for shared flat libraries, this is nonzero + * if %a5 points the current library's GOT. */ + .equ have_current_got, 0 + + /* Perform the equivalent of "<op> <target>", where <target> is + * a text address. <tmp> is available as a temporary address + * register. */ + .macro DO_TEXT op,target,tmp +#if defined __HAVE_SHARED_FLAT__ + .ifne have_current_got + move.l \target@GOT(%a5),\tmp + .else + move.l _current_shared_library_a5_offset_(%a5),\tmp + move.l \target@GOT(\tmp),\tmp + .endif + \op (\tmp) +#elif defined __PIC__ + lea \target-.-8,\tmp + \op (%pc,\tmp) +#else + \op \target +#endif + .endm + + /* Do "pea <target>" when <target> is a text address. + * <tmp> is available as a temporary register. */ + .macro PEA_TEXT target,tmp + DO_TEXT pea,\target,\tmp + .endm + + /* Likewise jsr. */ + .macro CALL target,tmp + DO_TEXT jsr,\target,\tmp + .endm + + /* Likewise jmp. */ + .macro JUMP target,tmp + DO_TEXT jmp,\target,\tmp + .endm + + /* Initialize the global pointer, if functions need to do that. */ + .macro INIT_GP +#if defined __HAVE_SHARED_FLAT__ + move.l %a5,-(%sp) + move.l _current_shared_library_a5_offset_(%a5),%a5 +#endif + .endm + + /* Undo the effects of INIT_GP. */ + .macro FINI_GP +#if defined __HAVE_SHARED_FLAT__ + move.l (%sp)+,%a5 +#endif + .endm diff --git a/libc/sysdeps/linux/m68k/setjmp.S b/libc/sysdeps/linux/m68k/setjmp.S index d7ca5dd6d..4adda0af5 100644 --- a/libc/sysdeps/linux/m68k/setjmp.S +++ b/libc/sysdeps/linux/m68k/setjmp.S @@ -5,6 +5,7 @@ #define _ASM #define _SETJMP_H #include <bits/setjmp.h> +#include "m68k_pic.S" .globl __sigsetjmp; .type __sigsetjmp,@function @@ -18,6 +19,5 @@ __sigsetjmp: fmovemx %fp2-%fp7, %a0@(JB_FPREGS) #endif clrl %d0 - lea __sigjmp_save-.-8, %a0 - jmp 0(%pc, %a0) + JUMP __sigjmp_save,%a0 |