aboutsummaryrefslogtreecommitdiff
path: root/bfd/bfd.c
diff options
context:
space:
mode:
Diffstat (limited to 'bfd/bfd.c')
-rw-r--r--bfd/bfd.c882
1 files changed, 882 insertions, 0 deletions
diff --git a/bfd/bfd.c b/bfd/bfd.c
new file mode 100644
index 0000000..d93bc72
--- /dev/null
+++ b/bfd/bfd.c
@@ -0,0 +1,882 @@
+ /* -*- C -*- */
+
+/*** bfd -- binary file diddling routines by Gumby Wallace of Cygnus Support.
+ Every definition in this file should be exported and declared
+ in bfd.c. If you don't want it to be user-visible, put it in
+ libbfd.c!
+*/
+
+/* Copyright (C) 1990, 1991 Free Software Foundation, Inc.
+
+This file is part of BFD, the Binary File Diddler.
+
+BFD is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 1, or (at your option)
+any later version.
+
+BFD 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with BFD; see the file COPYING. If not, write to
+the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* $Id$ */
+#include "sysdep.h"
+#include "bfd.h"
+#include "libbfd.h"
+
+short _bfd_host_big_endian = 0x0100;
+ /* Accessing the above as (*(char*)&_bfd_host_big_endian), will
+ * return 1 if the host is big-endian, 0 otherwise.
+ * (See HOST_IS_BIG_ENDIAN_P in bfd.h.)
+ */
+
+
+
+
+/** Error handling
+ o - Most functions return nonzero on success (check doc for
+ precise semantics); 0 or NULL on error.
+ o - Internal errors are documented by the value of bfd_error.
+ If that is system_call_error then check errno.
+ o - The easiest way to report this to the user is to use bfd_perror.
+*/
+
+bfd_ec bfd_error = no_error;
+
+char *bfd_errmsgs[] = {"No error",
+ "System call error",
+ "Invalid target",
+ "File in wrong format",
+ "Invalid operation",
+ "Memory exhausted",
+ "No symbols",
+ "No relocation info",
+ "No more archived files",
+ "Malformed archive",
+ "Symbol not found",
+ "File format not recognized",
+ "File format is ambiguous",
+ "Section has no contents",
+ "#<Invalid error code>"
+ };
+
+#if !defined(ANSI_LIBRARIES)
+char *
+strerror (code)
+ int code;
+{
+ extern int sys_nerr;
+ extern char *sys_errlist[];
+
+ return (((code < 0) || (code >= sys_nerr)) ? "(unknown error)" :
+ sys_errlist [code]);
+}
+#endif /* not ANSI_LIBRARIES */
+
+char *
+bfd_errmsg (error_tag)
+ bfd_ec error_tag;
+{
+ extern int errno;
+
+ if (error_tag == system_call_error)
+ return strerror (errno);
+
+ if ((((int)error_tag <(int) no_error) ||
+ ((int)error_tag > (int)invalid_error_code)))
+ error_tag = invalid_error_code;/* sanity check */
+
+ return bfd_errmsgs [(int)error_tag];
+}
+
+void
+bfd_perror (message)
+ char *message;
+{
+ if (bfd_error == system_call_error)
+ perror(message); /* must be system error then... */
+ else {
+ if (message == NULL || *message == '\0')
+ fprintf (stderr, "%s\n", bfd_errmsg (bfd_error));
+ else
+ fprintf (stderr, "%s: %s\n", message, bfd_errmsg (bfd_error));
+ }
+}
+
+/* for error messages */
+char *
+bfd_format_string (format)
+ bfd_format format;
+{
+ if (((int)format <(int) bfd_unknown) || ((int)format >=(int) bfd_type_end)) return "invalid";
+
+ switch (format) {
+ case bfd_object: return "object"; /* linker/assember/compiler output */
+ case bfd_archive: return "archive"; /* object archive file */
+ case bfd_core: return "core"; /* core dump */
+ default: return "unknown";
+ }
+}
+
+/** Target configurations */
+
+extern bfd_target *target_vector[];
+
+/* Returns a pointer to the transfer vector for the object target
+ named target_name. If target_name is NULL, chooses the one in the
+ environment variable GNUTARGET; if that is null or not defined then
+ the first entry in the target list is chosen. Passing in the
+ string "default" or setting the environment variable to "default"
+ will cause the first entry in the target list to be returned. */
+
+bfd_target *
+bfd_find_target (target_name)
+ char *target_name;
+{
+ bfd_target **target;
+ extern char *getenv ();
+ char *targname = (target_name ? target_name : getenv ("GNUTARGET"));
+
+ /* This is safe; the vector cannot be null */
+ if (targname == NULL || !strcmp (targname, "default"))
+ return target_vector[0];
+
+ for (target = &target_vector[0]; *target != NULL; target++) {
+ if (!strcmp (targname, (*target)->name))
+ return *target;
+ }
+
+ bfd_error = invalid_target;
+ return NULL;
+}
+
+/* Returns a freshly-consed, NULL-terminated vector of the names of all the
+ valid bfd targets. Do not modify the names */
+
+char **
+bfd_target_list ()
+{
+ int vec_length= 0;
+ bfd_target **target;
+ char **name_list, **name_ptr;
+
+ for (target = &target_vector[0]; *target != NULL; target++)
+ vec_length++;
+
+ name_ptr = name_list = (char **) zalloc ((vec_length + 1) * sizeof (char **));
+
+ if (name_list == NULL) {
+ bfd_error = no_memory;
+ return NULL;
+ }
+
+ for (target = &target_vector[0]; *target != NULL; target++)
+ *(name_ptr++) = (*target)->name;
+
+ return name_list;
+}
+
+/** Init a bfd for read of the proper format.
+ */
+
+/* We should be able to find out if the target was defaulted or user-specified.
+ If the user specified the target explicitly then we should do no search.
+ I guess the best way to do this is to pass an extra argument which specifies
+ the DWIM. */
+
+/* I have chanegd this always to set the filepos to the origin before
+ guessing. -- Gumby, 14 Februar 1991*/
+
+boolean
+bfd_check_format (abfd, format)
+ bfd *abfd;
+ bfd_format format;
+{
+#if obsolete
+ file_ptr filepos;
+#endif
+ bfd_target **target, *save_targ, *right_targ;
+ int match_count;
+
+ if (!bfd_read_p (abfd) ||
+ ((int)(abfd->format) < (int)bfd_unknown) ||
+ ((int)(abfd->format) >= (int)bfd_type_end)) {
+ bfd_error = invalid_operation;
+ return false;
+ }
+
+ if (abfd->format != bfd_unknown) return (abfd->format == format) ? true:false;
+
+ /* presume the answer is yes */
+ abfd->format = format;
+
+#if obsolete
+ filepos = bfd_tell (abfd);
+#endif
+ bfd_seek (abfd, (file_ptr)0, SEEK_SET); /* instead, rewind! */
+
+
+ right_targ = BFD_SEND_FMT (abfd, _bfd_check_format, (abfd));
+ if (right_targ) {
+ abfd->xvec = right_targ; /* Set the target as returned */
+ return true; /* File position has moved, BTW */
+ }
+
+ /* This isn't a <format> file in the specified or defaulted target type.
+ See if we recognize it for any other target type. (We check them
+ all to make sure it's uniquely recognized.) */
+
+ save_targ = abfd->xvec;
+ match_count = 0;
+ right_targ = 0;
+
+ for (target = target_vector; *target != NULL; target++) {
+ bfd_target *temp;
+
+ abfd->xvec = *target; /* Change BFD's target temporarily */
+#if obsolete
+ bfd_seek (abfd, filepos, SEEK_SET); /* Restore original file position */
+#endif
+ bfd_seek (abfd, (file_ptr)0, SEEK_SET);
+ temp = BFD_SEND_FMT (abfd, _bfd_check_format, (abfd));
+ if (temp) { /* This format checks out as ok! */
+ right_targ = temp;
+ match_count++;
+ }
+ }
+
+ if (match_count == 1) {
+ abfd->xvec = right_targ; /* Change BFD's target permanently */
+ return true; /* File position has moved, BTW */
+ }
+
+ abfd->xvec = save_targ; /* Restore original target type */
+ abfd->format = bfd_unknown; /* Restore original format */
+ bfd_error = ((match_count == 0) ? file_not_recognized :
+ file_ambiguously_recognized);
+#if obsolete
+ bfd_seek (abfd, filepos, SEEK_SET); /* Restore original file position */
+#endif
+ return false;
+}
+
+boolean
+bfd_set_format (abfd, format)
+ bfd *abfd;
+ bfd_format format;
+{
+ file_ptr filepos;
+
+ if (bfd_read_p (abfd) ||
+ ((int)abfd->format < (int)bfd_unknown) ||
+ ((int)abfd->format >= (int)bfd_type_end)) {
+ bfd_error = invalid_operation;
+ return false;
+ }
+
+ if (abfd->format != bfd_unknown) return (abfd->format == format) ? true:false;
+
+ /* presume the answer is yes */
+ abfd->format = format;
+
+ filepos = bfd_tell (abfd);
+
+ if (!BFD_SEND_FMT (abfd, _bfd_set_format, (abfd))) {
+ abfd->format = bfd_unknown;
+ bfd_seek (abfd, filepos, SEEK_SET);
+ return false;
+ }
+
+ return true;
+}
+
+/* Hack object and core file sections */
+
+sec_ptr
+bfd_get_section_by_name (abfd, name)
+ bfd *abfd;
+ char *name;
+{
+ asection *sect;
+
+ for (sect = abfd->sections; sect != NULL; sect = sect->next)
+ if (!strcmp (sect->name, name)) return sect;
+ return NULL;
+}
+
+/* If you try to create a section with a name which is already in use,
+ returns the old section by that name instead. */
+sec_ptr
+bfd_make_section (abfd, name)
+ bfd *abfd;
+ char *name;
+{
+ asection *newsect;
+ asection ** prev = &abfd->sections;
+ asection * sect = abfd->sections;
+
+ if (abfd->output_has_begun) {
+ bfd_error = invalid_operation;
+ return NULL;
+ }
+
+ while (sect) {
+ if (!strcmp(sect->name, name)) return sect;
+ prev = &sect->next;
+ sect = sect->next;
+ }
+
+ newsect = (asection *) zalloc (sizeof (asection));
+ if (newsect == NULL) {
+ bfd_error = no_memory;
+ return NULL;
+ }
+
+ newsect->name = name;
+ newsect->index = abfd->section_count++;
+ newsect->flags = SEC_NO_FLAGS;
+
+#if ignore /* the compiler doesn't know that zalloc clears the storage */
+ newsect->userdata = 0;
+ newsect->next = (asection *)NULL;
+ newsect->relocation = (arelent *)NULL;
+ newsect->reloc_count = 0;
+ newsect->line_filepos =0;
+#endif
+ if (BFD_SEND (abfd, _new_section_hook, (abfd, newsect)) != true) {
+ free (newsect);
+ return NULL;
+ }
+
+ *prev = newsect;
+ return newsect;
+}
+
+/* Call operation on each section. Operation gets three args: the bfd,
+ the section, and a void * pointer (whatever the user supplied). */
+
+/* This is attractive except that without lexical closures its use is hard
+ to make reentrant. */
+/*VARARGS2*/
+void
+bfd_map_over_sections (abfd, operation, user_storage)
+ bfd *abfd;
+ void (*operation)();
+ void *user_storage;
+{
+ asection *sect;
+ int i = 0;
+
+ for (sect = abfd->sections; sect != NULL; i++, sect = sect->next)
+ (*operation) (abfd, sect, user_storage);
+
+ if (i != abfd->section_count) /* Debugging */
+ abort();
+}
+
+boolean
+bfd_set_section_flags (abfd, section, flags)
+ bfd *abfd;
+ sec_ptr section;
+ flagword flags;
+{
+ if ((flags & bfd_applicable_section_flags (abfd)) != flags) {
+ bfd_error = invalid_operation;
+ return false;
+ }
+
+ section->flags = flags;
+return true;
+}
+
+
+boolean
+bfd_set_section_size (abfd, ptr, val)
+ bfd *abfd;
+ sec_ptr ptr;
+ unsigned long val;
+{
+ /* Once you've started writing to any section you cannot create or change
+ the size of any others. */
+
+ if (abfd->output_has_begun) {
+ bfd_error = invalid_operation;
+ return false;
+ }
+
+ ptr->size = val;
+
+ return true;
+}
+
+boolean
+bfd_set_section_contents (abfd, section, location, offset, count)
+ bfd *abfd;
+ sec_ptr section;
+void *location;
+ file_ptr offset;
+ int count;
+{
+ if (!(bfd_get_section_flags(abfd, section) &
+ SEC_HAS_CONTENTS)) {
+ bfd_error = no_contents;
+ return(false);
+ } /* if section has no contents */
+
+ if (BFD_SEND (abfd, _bfd_set_section_contents,
+ (abfd, section, location, offset, count))) {
+ abfd->output_has_begun = true;
+ return true;
+ }
+
+ return false;
+}
+
+boolean
+bfd_get_section_contents (abfd, section, location, offset, count)
+ bfd *abfd;
+ sec_ptr section;
+ void *location;
+ file_ptr offset;
+ int count;
+{
+ if (section->flags & SEC_CONSTRUCTOR) {
+ memset(location, 0, count);
+ return true;
+ }
+ else {
+ return (BFD_SEND (abfd, _bfd_get_section_contents,
+ (abfd, section, location, offset, count)));
+ }
+}
+
+
+/** Some core file info commands */
+
+/* Returns a read-only string explaining what program was running when
+ it failed. */
+
+char *
+bfd_core_file_failing_command (abfd)
+ bfd *abfd;
+{
+ if (abfd->format != bfd_core) {
+ bfd_error = invalid_operation;
+ return NULL;
+ }
+ return BFD_SEND (abfd, _core_file_failing_command, (abfd));
+}
+
+int
+bfd_core_file_failing_signal (abfd)
+ bfd *abfd;
+{
+ if (abfd->format != bfd_core) {
+ bfd_error = invalid_operation;
+ return NULL;
+ }
+ return BFD_SEND (abfd, _core_file_failing_signal, (abfd));
+}
+
+boolean
+core_file_matches_executable_p (core_bfd, exec_bfd)
+ bfd *core_bfd, *exec_bfd;
+{
+ if ((core_bfd->format != bfd_core) || (exec_bfd->format != bfd_object)) {
+ bfd_error = wrong_format;
+ return false;
+ }
+
+ return BFD_SEND (core_bfd, _core_file_matches_executable_p, (core_bfd, exec_bfd));
+}
+
+/** Symbols */
+
+boolean
+bfd_set_symtab (abfd, location, symcount)
+ bfd *abfd;
+ asymbol **location;
+ unsigned int symcount;
+{
+ if ((abfd->format != bfd_object) || (bfd_read_p (abfd))) {
+ bfd_error = invalid_operation;
+ return false;
+ }
+
+ bfd_get_outsymbols (abfd) = location;
+ bfd_get_symcount (abfd) = symcount;
+ return true;
+}
+
+/* returns the number of octets of storage required */
+unsigned int
+get_reloc_upper_bound (abfd, asect)
+ bfd *abfd;
+ sec_ptr asect;
+{
+ if (abfd->format != bfd_object) {
+ bfd_error = invalid_operation;
+ return 0;
+ }
+
+ return BFD_SEND (abfd, _get_reloc_upper_bound, (abfd, asect));
+}
+
+unsigned int
+bfd_canonicalize_reloc (abfd, asect, location, symbols)
+ bfd *abfd;
+ sec_ptr asect;
+ arelent **location;
+ asymbol **symbols;
+{
+ if (abfd->format != bfd_object) {
+ bfd_error = invalid_operation;
+ return 0;
+ }
+
+ return BFD_SEND (abfd, _bfd_canonicalize_reloc, (abfd, asect, location, symbols));
+}
+
+void
+bfd_print_symbol_vandf(file, symbol)
+void *file;
+asymbol *symbol;
+{
+ flagword type = symbol->flags;
+ if (symbol->section != (asection *)NULL)
+ {
+ fprintf(file,"%08lx ", symbol->value+symbol->section->vma);
+ }
+ else
+ {
+ fprintf(file,"%08lx ", symbol->value);
+ }
+ fprintf(file,"%c%c%c%c%c%c%c",
+ (type & BSF_LOCAL) ? 'l':' ',
+ (type & BSF_GLOBAL) ? 'g' : ' ',
+ (type & BSF_IMPORT) ? 'i' : ' ',
+ (type & BSF_EXPORT) ? 'e' : ' ',
+ (type & BSF_UNDEFINED) ? 'u' : ' ',
+ (type & BSF_FORT_COMM) ? 'c' : ' ',
+ (type & BSF_DEBUGGING) ? 'd' :' ');
+
+}
+
+
+boolean
+bfd_set_file_flags (abfd, flags)
+ bfd *abfd;
+ flagword flags;
+{
+ if (abfd->format != bfd_object) {
+ bfd_error = wrong_format;
+ return false;
+ }
+
+ if (bfd_read_p (abfd)) {
+ bfd_error = invalid_operation;
+ return false;
+ }
+
+ if ((flags & bfd_applicable_file_flags (abfd)) != flags) {
+ bfd_error = invalid_operation;
+ return false;
+ }
+
+ bfd_get_file_flags (abfd) = flags;
+return true;
+}
+
+
+void
+bfd_set_reloc (ignore_abfd, asect, location, count)
+ bfd *ignore_abfd;
+ sec_ptr asect;
+ arelent **location;
+ unsigned int count;
+{
+ asect->orelocation = location;
+ asect->reloc_count = count;
+}
+/*
+If an output_bfd is supplied to this function the generated image
+will be relocatable, the relocations are copied to the output file
+after they have been changed to reflect the new state of the world.
+There are two ways of reflecting the results of partial linkage in an
+output file; by modifying the output data in place, and by modifying
+the relocation record. Some native formats (eg basic a.out and basic
+coff) have no way of specifying an addend in the relocation type, so
+the addend has to go in the output data. This is no big deal since in
+these formats the output data slot will always be big enough for the
+addend. Complex reloc types with addends were invented to solve just
+this problem.
+*/
+
+bfd_reloc_status_enum_type
+bfd_perform_relocation(abfd,
+ reloc_entry,
+ data,
+ input_section,
+ output_bfd)
+bfd *abfd;
+arelent *reloc_entry;
+void *data;
+asection *input_section;
+bfd *output_bfd;
+{
+ bfd_vma relocation;
+ bfd_reloc_status_enum_type flag = bfd_reloc_ok;
+ bfd_vma relocation_before;
+ bfd_vma mask;
+ bfd_vma target_mask;
+ bfd_vma addr = reloc_entry->address ;
+ bfd_vma output_base = 0;
+ struct rint_struct *howto = reloc_entry->howto;
+ asection *reloc_target_output_section;
+ asection *reloc_target_input_section;
+ asymbol *symbol;
+
+ if (reloc_entry->sym_ptr_ptr) {
+ symbol = *( reloc_entry->sym_ptr_ptr);
+ if ((symbol->flags & BSF_UNDEFINED) && output_bfd == (bfd *)NULL) {
+ flag = bfd_reloc_undefined;
+ }
+ }
+ else {
+ symbol = (asymbol*)NULL;
+ }
+
+ if (howto->special_function) {
+ bfd_reloc_status_enum_type cont;
+ cont = howto->special_function(abfd,
+ reloc_entry,
+ symbol,
+ data,
+ input_section);
+ if (cont != bfd_reloc_continue) return cont;
+ }
+
+ /*
+ Work out which section the relocation is targetted at and the
+ initial relocation command value.
+ */
+
+
+ if (symbol != (asymbol *)NULL){
+ if (symbol->flags & BSF_FORT_COMM) {
+ relocation = 0;
+ }
+ else {
+ relocation = symbol->value;
+ }
+ if (symbol->section != (asection *)NULL)
+ {
+ reloc_target_input_section = symbol->section;
+ }
+ else {
+ reloc_target_input_section = (asection *)NULL;
+ }
+ }
+ else if (reloc_entry->section != (asection *)NULL)
+ {
+ relocation = 0;
+ reloc_target_input_section = reloc_entry->section;
+ }
+ else {
+ relocation = 0;
+ reloc_target_input_section = (asection *)NULL;
+ }
+
+
+ if (reloc_target_input_section != (asection *)NULL) {
+
+ reloc_target_output_section =
+ reloc_target_input_section->output_section;
+
+ if (output_bfd && howto->partial_inplace==false) {
+ output_base = 0;
+ }
+ else {
+ output_base = reloc_target_output_section->vma;
+
+ }
+
+ relocation += output_base + reloc_target_input_section->output_offset;
+ }
+
+ relocation += reloc_entry->addend ;
+
+
+ if(reloc_entry->address > (bfd_vma)(input_section->size))
+ {
+ return bfd_reloc_outofrange;
+ }
+
+
+ if (howto->pc_relative == true)
+ {
+ /*
+ Anything which started out as pc relative should end up that
+ way too
+ */
+
+ relocation -=
+ output_base + input_section->output_offset;
+
+ }
+
+ if (output_bfd!= (bfd *)NULL && howto->partial_inplace == false) {
+ /*
+ This is a partial relocation, and we want to apply the relocation
+ to the reloc entry rather than the raw data. Modify the reloc
+ inplace to reflect what we now know.
+ */
+ reloc_entry->addend = relocation ;
+ reloc_entry->section = reloc_target_input_section;
+ if (reloc_target_input_section != (asection *)NULL) {
+ /* If we know the output section we can forget the symbol */
+ reloc_entry->sym_ptr_ptr = (asymbol**)NULL;
+ }
+ reloc_entry->address +=
+ input_section->output_offset;
+ }
+ else {
+ reloc_entry->addend = 0;
+
+
+ /*
+ Either we are relocating all the way, or we don't want to apply
+ the relocation to the reloc entry (probably because there isn't
+ any room in the output format to describe addends to relocs)
+ */
+ relocation >>= howto->rightshift;
+ if (howto->bitsize == 32) {
+ mask = ~0;
+ }
+ else {
+ mask = (1L << howto->bitsize) - 1 ;
+ mask |= mask - 1; /* FIXME, what is this? */
+ }
+
+ relocation &= mask;
+
+ /* Shift everything up to where it's going to be used */
+
+ relocation <<= howto->bitpos;
+ mask <<= howto->bitpos;
+ target_mask = ~mask;
+
+ /* Wait for the day when all have the mask in them */
+
+ BFD_ASSERT(howto->mask == mask);
+
+
+
+ relocation_before = relocation;
+
+
+ switch (howto->size)
+ {
+ case 0:
+ {
+ char x = bfd_getchar(abfd, (char *)data + addr);
+ relocation += x & mask;
+ bfd_putchar(abfd,
+ ( x & target_mask) | ( relocation & mask),
+ (unsigned char *) data + addr);
+ }
+ break;
+
+ case 1:
+ {
+ short x = bfd_getshort(abfd, (bfd_byte *)data + addr);
+ relocation += x & mask;
+ bfd_putshort(abfd, ( x & target_mask) | (relocation & mask),
+ (unsigned char *)data + addr);
+ }
+ break;
+ case 2:
+ {
+ long x = bfd_getlong(abfd, (bfd_byte *) data + addr);
+ relocation += x & mask;
+ bfd_putlong(abfd, ( x & target_mask) | (relocation & mask),
+ (bfd_byte *)data + addr);
+ }
+ break;
+ case 3:
+ /* Do nothing */
+ break;
+ default:
+ return bfd_reloc_other;
+ }
+
+ /* See if important parts of the relocation were chopped to make
+ it fit into the relocation field. (ie are there any significant
+ bits left over after the masking ? */
+ if ((relocation_before & target_mask) != 0 &&
+ howto->complain_on_overflow == true)
+ {
+ /* Its ok if the bit which fell off is */
+ return bfd_reloc_overflow;
+ }
+ }
+
+ return flag;
+}
+
+void
+bfd_assert(file, line)
+char *file;
+int line;
+{
+ printf("bfd assertion fail %s:%d\n",file,line);
+}
+
+
+boolean
+bfd_set_start_address(abfd, vma)
+bfd *abfd;
+bfd_vma vma;
+{
+ abfd->start_address = vma;
+ return true;
+}
+
+
+bfd_vma bfd_log2(x)
+bfd_vma x;
+{
+ bfd_vma result = 0;
+ while ( (bfd_vma)(1<< result) < x)
+ result++;
+ return result;
+}
+
+/* bfd_get_mtime: Return cached file modification time (e.g. as read
+ from archive header for archive members, or from file system if we have
+ been called before); else determine modify time, cache it, and
+ return it. */
+
+long
+bfd_get_mtime (abfd)
+ bfd *abfd;
+{
+ FILE *fp;
+ struct stat buf;
+
+ if (abfd->mtime_set)
+ return abfd->mtime;
+
+ fp = bfd_cache_lookup (abfd);
+ if (0 != fstat (fileno (fp), &buf))
+ return 0;
+
+ abfd->mtime_set = true;
+ abfd->mtime = buf.st_mtime;
+ return abfd->mtime;
+}