[users/roland/ld-depfile] ld, gold: Add --dependency-file option.

Message ID CAB=4xhpASM9Ox3zRa6EE6MnSoUMBHAN4A1rG62rOgo_wTWMDxQ@mail.gmail.com
State New
Headers show
Series
  • [users/roland/ld-depfile] ld, gold: Add --dependency-file option.
Related show

Commit Message

Alan Modra via Binutils June 13, 2020, 5:26 a.m.
[I think my MUA may be mangling the patch, but it's in git as branch
users/roland/ld-depfile.]

I think the utility is self-evident.  Ok for trunk?


Thanks,
Roland


gold/
2020-06-12  Roland McGrath  <mcgrathr@google.com>

* options.h (class General_options): Add --dependency-file option.
* fileread.cc (File_read::files_read): New static variable.
(File_read::write_dependency_file): New static member function.
* fileread.h (class File_read): Declare them.
* layout.cc (Close_task_runner::run): Call write_dependency_file
if --dependency-file was passed.

ld/
2020-06-12  Roland McGrath  <mcgrathr@google.com>

* NEWS: Note --dependency-file.
* ld.texi (Options): Document --dependency-file.
* ldlex.h (enum option_values): Add OPTION_DEPENDENCY_FILE.
* ld.h (ld_config_type): New member dependency_file.
* lexsup.c (ld_options, parse_args): Parse --dependency-file.
* ldmain.c (struct dependency_file): New type.
(dependency_files, dependency_files_tail): New static variables.
(track_dependency_files): New function.
(write_dependency_file): New function.
(main): Call it when --dependency-file was passed.
* ldfile.c (ldfile_try_open_bfd): Call track_dependency_files.
* ldelf.c (ldelf_try_needed): Likewise.
* pe-dll.c (pe_implied_import_dll): Likewise.

Comments

Fangrui Song June 13, 2020, 4:44 p.m. | #1
On 2020-06-12, Roland McGrath via Binutils wrote:
>[I think my MUA may be mangling the patch, but it's in git as branch

>users/roland/ld-depfile.]


You can set .gitconfig sendemail.smtpServer to bypass the annoying MUA
and use neomutt/mutt via IMAP:)

>I think the utility is self-evident.  Ok for trunk?


Sorry, but I am not convinced this option provides additional features.
You'd want to link https://sourceware.org/bugzilla/show_bug.cgi?id=22843

tl;dr -t satisfies your needs: { echo -n 'a: '; clang a.o -o a -Wl,-t | sed 's/(.*//' | sort -u | sed '$!s/$/ \\/';} > a.d

Longer answer: https://reviews.llvm.org/D65430#1616208
and see another reply https://reviews.llvm.org/D65430#1755644


>Thanks,

>Roland

>

>

>gold/

>2020-06-12  Roland McGrath  <mcgrathr@google.com>

>

>* options.h (class General_options): Add --dependency-file option.

>* fileread.cc (File_read::files_read): New static variable.

>(File_read::write_dependency_file): New static member function.

>* fileread.h (class File_read): Declare them.

>* layout.cc (Close_task_runner::run): Call write_dependency_file

>if --dependency-file was passed.

>

>ld/

>2020-06-12  Roland McGrath  <mcgrathr@google.com>

>

>* NEWS: Note --dependency-file.

>* ld.texi (Options): Document --dependency-file.

>* ldlex.h (enum option_values): Add OPTION_DEPENDENCY_FILE.

>* ld.h (ld_config_type): New member dependency_file.

>* lexsup.c (ld_options, parse_args): Parse --dependency-file.

>* ldmain.c (struct dependency_file): New type.

>(dependency_files, dependency_files_tail): New static variables.

>(track_dependency_files): New function.

>(write_dependency_file): New function.

>(main): Call it when --dependency-file was passed.

>* ldfile.c (ldfile_try_open_bfd): Call track_dependency_files.

>* ldelf.c (ldelf_try_needed): Likewise.

>* pe-dll.c (pe_implied_import_dll): Likewise.

>

>diff --git a/gold/fileread.cc b/gold/fileread.cc

>index bebe0aba8d..ea89723168 100644

>--- a/gold/fileread.cc

>+++ b/gold/fileread.cc

>@@ -124,6 +124,7 @@ static Initialize_lock

>file_counts_initialize_lock(&file_counts_lock);

> unsigned long long File_read::total_mapped_bytes;

> unsigned long long File_read::current_mapped_bytes;

> unsigned long long File_read::maximum_mapped_bytes;

>+std::set<std::string> File_read::files_read;

>

> // Class File_read::View.

>

>@@ -211,6 +212,8 @@ File_read::open(const Task* task, const std::string& name)

>       gold_debug(DEBUG_FILES, "Attempt to open %s succeeded",

>  this->name_.c_str());

>       this->token_.add_writer(task);

>+      Hold_optional_lock hl(file_counts_lock);

>+      File_read::files_read.insert(this->name_);

>     }

>

>   return this->descriptor_ >= 0;

>@@ -1138,4 +1141,25 @@ Input_file::open_binary(const Task* task, const

>std::string& name)

>    binary_to_elf.converted_size());

> }

>

>+void

>+File_read::write_dependency_file(const char* dependency_file_name,

>+                                 const char* output_file_name)

>+{

>+  FILE *depfile = fopen(dependency_file_name, "w");

>+

>+  fprintf(depfile, "%s:", output_file_name);

>+  for (std::set<std::string>::const_iterator it = files_read.begin();

>+       it != files_read.end();

>+       ++it)

>+    fprintf(depfile, " \\\n  %s", it->c_str());

>+  fprintf(depfile, "\n");

>+

>+  for (std::set<std::string>::const_iterator it = files_read.begin();

>+       it != files_read.end();

>+       ++it)

>+    fprintf(depfile, "\n%s:\n", it->c_str());

>+

>+  fclose(depfile);

>+}

>+

> } // End namespace gold.

>diff --git a/gold/fileread.h b/gold/fileread.h

>index cf92367c2c..005d2e28c8 100644

>--- a/gold/fileread.h

>+++ b/gold/fileread.h

>@@ -27,6 +27,7 @@

>

> #include <list>

> #include <map>

>+#include <set>

> #include <string>

> #include <vector>

>

>@@ -207,6 +208,11 @@ class File_read

>   static void

>   print_stats();

>

>+  // Write the dependency file listing all files read.

>+  static void

>+  write_dependency_file(const char* dependency_file_name,

>+                        const char* output_file_name);

>+

>   // Return the open file descriptor (for plugins).

>   int

>   descriptor()

>@@ -214,7 +220,7 @@ class File_read

>     this->reopen_descriptor();

>     return this->descriptor_;

>   }

>-

>+

>   // Return the file last modification time.  Calls gold_fatal if the stat

>   // system call failed.

>   Timespec

>@@ -247,6 +253,9 @@ class File_read

>   // --stats.

>   static unsigned long long maximum_mapped_bytes;

>

>+  // Set of names of all files read.

>+  static std::set<std::string> files_read;

>+

>   // A view into the file.

>   class View

>   {

>diff --git a/gold/layout.cc b/gold/layout.cc

>index be437f3900..8510210868 100644

>--- a/gold/layout.cc

>+++ b/gold/layout.cc

>@@ -6153,6 +6153,10 @@ Close_task_runner::run(Workqueue*, const Task*)

>   if (this->options_->oformat_enum() != General_options::OBJECT_FORMAT_ELF)

>     this->layout_->write_binary(this->of_);

>

>+  if (this->options_->dependency_file())

>+    File_read::write_dependency_file(this->options_->dependency_file(),

>+                                     this->options_->output_file_name());

>+

>   this->of_->close();

> }

>

>diff --git a/gold/options.h b/gold/options.h

>index b2059d984c..eaf8727ba3 100644

>--- a/gold/options.h

>+++ b/gold/options.h

>@@ -472,7 +472,7 @@ struct Struct_special : public Struct_var

>   options::String_set::const_iterator   \

>   varname__##_end() const   \

>   { return this->varname__##_.value.end(); }                              \

>-                                                                          \

>+   \

>   options::String_set::size_type                                          \

>   varname__##_size() const                                                \

>   { return this->varname__##_.value.size(); }                             \

>@@ -800,6 +800,10 @@ class General_options

>        N_("Do not demangle C++ symbols in log messages"),

>        NULL);

>

>+  DEFINE_string(dependency_file, options::TWO_DASHES, '\0', NULL,

>+ N_("Write a dependency file listing all files read"),

>+ N_("FILE"));

>+

>   DEFINE_bool(detect_odr_violations, options::TWO_DASHES, '\0', false,

>        N_("Look for violations of the C++ One Definition Rule"),

>        N_("Do not look for violations of the C++ One Definition Rule"));

>diff --git a/ld/NEWS b/ld/NEWS

>index 485e1cf5b9..f6ff062d0d 100644

>--- a/ld/NEWS

>+++ b/ld/NEWS

>@@ -25,6 +25,10 @@

>   searched relative to the directory of the linker script before other search

>   paths.

>

>+* Add command-line option --dependency-file to write a Make-style dependency

>+  file listing the input files consulted by the linker, like the files written

>+  by the compiler's -M -MP options.

>+

> Changes in 2.34:

>

> * The ld check for "PHDR segment not covered by LOAD segment" is more

>diff --git a/ld/ld.h b/ld/ld.h

>index 71fd781267..1790dc81a6 100644

>--- a/ld/ld.h

>+++ b/ld/ld.h

>@@ -286,6 +286,8 @@ typedef struct

>   char *map_filename;

>   FILE *map_file;

>

>+  char *dependency_file;

>+

>   unsigned int split_by_reloc;

>   bfd_size_type split_by_file;

>

>diff --git a/ld/ld.texi b/ld/ld.texi

>index bf474d4c62..dc22aee799 100644

>--- a/ld/ld.texi

>+++ b/ld/ld.texi

>@@ -893,6 +893,15 @@ Use @var{output} as the name for the program

>produced by @command{ld}; if this

> option is not specified, the name @file{a.out} is used by default.  The

> script command @code{OUTPUT} can also specify the output file name.

>

>+@kindex --dependency-file=@var{depfile}

>+@cindex dependency file

>+@item --dependency-file=@var{depfile}

>+Write a @dfn{dependency file} to @var{depfile}.  This file contains a rule

>+suitable for @code{make} describing the output file and all the input files

>+that were read to produce it.  The output is similar to the compiler's output

>+with @samp{-M -MP} (@pxref{Preprocessor Options,, Options Controlling the

>+Preprocessor, gcc.info, Using the GNU Compiler Collection}).

>+

> @kindex -O @var{level}

> @cindex generating optimized output

> @item -O @var{level}

>diff --git a/ld/ldelf.c b/ld/ldelf.c

>index 8f2167e889..6fa09cfe6f 100644

>--- a/ld/ldelf.c

>+++ b/ld/ldelf.c

>@@ -262,6 +262,8 @@ ldelf_try_needed (struct dt_needed *needed, int

>force, int is_linux)

>       return FALSE;

>     }

>

>+  track_dependency_files (name);

>+

>   /* Linker needs to decompress sections.  */

>   abfd->flags |= BFD_DECOMPRESS;

>

>@@ -1065,7 +1067,7 @@ ldelf_after_open (int use_libpath, int native,

>int is_linux, int is_freebsd,

>  }

>       return;

>     }

>-

>+

>   if (!link_info.traditional_format)

>     {

>       bfd *elfbfd = NULL;

>diff --git a/ld/ldfile.c b/ld/ldfile.c

>index 60b28d3f0c..d5f10663eb 100644

>--- a/ld/ldfile.c

>+++ b/ld/ldfile.c

>@@ -142,6 +142,8 @@ ldfile_try_open_bfd (const char *attempt,

>       return FALSE;

>     }

>

>+  track_dependency_files (attempt);

>+

>   /* Linker needs to decompress sections.  */

>   entry->the_bfd->flags |= BFD_DECOMPRESS;

>

>diff --git a/ld/ldlex.h b/ld/ldlex.h

>index 6388247b45..459b237aad 100644

>--- a/ld/ldlex.h

>+++ b/ld/ldlex.h

>@@ -154,6 +154,7 @@ enum option_values

>   OPTION_NO_PRINT_MAP_DISCARDED,

>   OPTION_NON_CONTIGUOUS_REGIONS,

>   OPTION_NON_CONTIGUOUS_REGIONS_WARNINGS,

>+  OPTION_DEPENDENCY_FILE,

> };

>

> /* The initial parser states.  */

>diff --git a/ld/ldmain.c b/ld/ldmain.c

>index e2c559ea3e..8d777b55c4 100644

>--- a/ld/ldmain.c

>+++ b/ld/ldmain.c

>@@ -159,6 +159,53 @@ static bfd_error_handler_type default_bfd_error_handler;

>

> struct bfd_link_info link_info;

>

>+struct dependency_file

>+{

>+  struct dependency_file *next;

>+  char *name;

>+};

>+

>+static struct dependency_file *dependency_files, *dependency_files_tail;

>+

>+void

>+track_dependency_files (const char *filename)

>+{

>+  struct dependency_file *dep

>+    = (struct dependency_file *) xmalloc (sizeof (*dep));

>+  dep->name = xstrdup (filename);

>+  dep->next = NULL;

>+  if (dependency_files == NULL)

>+    dependency_files = dep;

>+  else

>+    dependency_files_tail->next = dep;

>+  dependency_files_tail = dep;

>+}

>+

>+static void

>+write_dependency_file (void)

>+{

>+  FILE *out;

>+  struct dependency_file *dep;

>+

>+  out = fopen (config.dependency_file, FOPEN_WT);

>+  if (out == NULL)

>+    {

>+      einfo (_("%F%P: cannot open dependency file %s: %E\n"),

>+             config.dependency_file);

>+    }

>+

>+  fprintf (out, "%s:", output_filename);

>+

>+  for (dep = dependency_files; dep != NULL; dep = dep->next)

>+    fprintf (out, " \\\n  %s", dep->name);

>+

>+  fprintf (out, "\n");

>+  for (dep = dependency_files; dep != NULL; dep = dep->next)

>+    fprintf (out, "\n%s:\n", dep->name);

>+

>+  fclose (out);

>+}

>+

> static void

> ld_cleanup (void)

> {

>@@ -479,6 +526,9 @@ main (int argc, char **argv)

>   ldexp_finish ();

>   lang_finish ();

>

>+  if (config.dependency_file != NULL)

>+    write_dependency_file ();

>+

>   /* Even if we're producing relocatable output, some non-fatal errors should

>      be reported in the exit status.  (What non-fatal errors, if any, do we

>      want to ignore for relocatable output?)  */

>diff --git a/ld/ldmain.h b/ld/ldmain.h

>index 0f05821d1e..ac7db5720d 100644

>--- a/ld/ldmain.h

>+++ b/ld/ldmain.h

>@@ -58,5 +58,6 @@ extern void add_ysym (const char *);

> extern void add_wrap (const char *);

> extern void add_ignoresym (struct bfd_link_info *, const char *);

> extern void add_keepsyms_file (const char *);

>+extern void track_dependency_files (const char *);

>

> #endif

>diff --git a/ld/lexsup.c b/ld/lexsup.c

>index d84b334b34..f4e2ba1ee9 100644

>--- a/ld/lexsup.c

>+++ b/ld/lexsup.c

>@@ -111,6 +111,8 @@ static const struct ld_option ld_options[] =

>     'c', N_("FILE"), N_("Read MRI format linker script"), TWO_DASHES },

>   { {"dc", no_argument, NULL, 'd'},

>     'd', NULL, N_("Force common symbols to be defined"), ONE_DASH },

>+  { {"dependency-file", required_argument, NULL, OPTION_DEPENDENCY_FILE},

>+    '\0', N_("FILE"), N_("Write dependency file"), TWO_DASHES },

>   { {"dp", no_argument, NULL, 'd'},

>     '\0', NULL, NULL, ONE_DASH },

>   { {"force-group-allocation", no_argument, NULL,

>@@ -1631,6 +1633,10 @@ parse_args (unsigned argc, char **argv)

>  case OPTION_PRINT_MAP_DISCARDED:

>    config.print_map_discarded = TRUE;

>    break;

>+

>+        case OPTION_DEPENDENCY_FILE:

>+          config.dependency_file = optarg;

>+          break;

>  }

>     }

>

>diff --git a/ld/pe-dll.c b/ld/pe-dll.c

>index f72b658341..3e8fe1be9b 100644

>--- a/ld/pe-dll.c

>+++ b/ld/pe-dll.c

>@@ -3344,6 +3344,8 @@ pe_implied_import_dll (const char *filename)

>       return FALSE;

>     }

>

>+  track_dependency_files (filename);

>+

>   /* PEI dlls seem to be bfd_objects.  */

>   if (!bfd_check_format (dll, bfd_object))

>     {
Alan Modra via Binutils June 14, 2020, 12:04 a.m. | #2
> +  // Set of names of all files read.

> +  static std::set<std::string> files_read;


Please consider whether using Unordered_set (declared in system.h),
which uses a hashed set where supported, would be better than
std::set, which uses a binary tree. I'm OK either way, as long as
you've considered the two, though my guess would be that Unordered_set
would probably be better.

As for a dedicated option vs. post-processing -t output, I'm in favor
of the dedicated option. The -t output might be mixed in with other
stderr output from the linker, and isn't really suitable for
general-purpose automated usage like this. Also, as was mentioned in
the lld review thread, -t output doesn't mention archive libraries
that were searched but contributed nothing to the link.

-cary
Fangrui Song June 14, 2020, 6:22 p.m. | #3
On 2020-06-13, Cary Coutant via Binutils wrote:
>> +  // Set of names of all files read.

>> +  static std::set<std::string> files_read;

>

>Please consider whether using Unordered_set (declared in system.h),

>which uses a hashed set where supported, would be better than

>std::set, which uses a binary tree. I'm OK either way, as long as

>you've considered the two, though my guess would be that Unordered_set

>would probably be better.


std::unordered_set makes the output unstable across different systems.
I like a data structure which iterates elements by insertion order (like JavaScript,Python,Ruby).
This is even better than a std::set

>As for a dedicated option vs. post-processing -t output, I'm in favor

>of the dedicated option. The -t output might be mixed in with other

>stderr output from the linker, and isn't really suitable for

>general-purpose automated usage like this.


gold should print to stdout instead of stderr.
See https://sourceware.org/pipermail/binutils/2020-June/111696.html :)
(If it is ok, please check in it for me, because I don't have a write
acess)

>Also, as was mentioned in

>the lld review thread, -t output doesn't mention archive libraries

>that were searched but contributed nothing to the link.

>

>-cary


This is a valid point. However, we still have a problem of lingering
files in the dependency list, which will be flagged by the next make
invocation.
In the traditional make+cc model, -MP is needed
(https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html )
Alan Modra via Binutils June 15, 2020, 6:33 a.m. | #4
On Fri, Jun 12, 2020 at 10:26:10PM -0700, Roland McGrath via Binutils wrote:
> --- a/ld/ldelf.c

> +++ b/ld/ldelf.c

> @@ -262,6 +262,8 @@ ldelf_try_needed (struct dt_needed *needed, int

> force, int is_linux)

>        return FALSE;

>      }

> 

> +  track_dependency_files (name);

> +

>    /* Linker needs to decompress sections.  */

>    abfd->flags |= BFD_DECOMPRESS;

> 


This should be moved later in the function, at least past the point
where incompatible library versions are excluded.  I think I'd put it
after the point where we exclude libc.so.1 being loaded if we've
already loaded libc.so (when libc.so is a symlink to libc.so.1).

-- 
Alan Modra
Australia Development Lab, IBM
Alan Modra via Binutils June 15, 2020, 9:13 p.m. | #5
On Sat, Jun 13, 2020 at 9:44 AM Fangrui Song <i@maskray.me> wrote:
> Sorry, but I am not convinced this option provides additional features.


You are mistaken.

> You'd want to link https://sourceware.org/bugzilla/show_bug.cgi?id=22843


I hadn't noticed it had been filed.  I've updated the ChangeLog items
to mention it and updated the bug.
Thanks for the pointer!

> tl;dr -t satisfies your needs: { echo -n 'a: '; clang a.o -o a -Wl,-t | sed 's/(.*//' | sort -u | sed '$!s/$/ \\/';} > a.d


This is frankly ludicrous on its face, even if it were technically
accurate (which it's not).

The possibility of a ridiculous kludge is not a reason to avoid adding
a sensible feature that is straightforward to use and fits existing
models.

A kludge like that is hopelessly impractical to integration into build
systems, which is where this feature is useful.

Moreover, it's not semantically equivalent.  While it's true that ld
does print -t output on stdout (gold is incompatible in putting it on
stderr), it's not true that -t output has equivalent information.

The criterion for -t output is "input object file was included in the
link".  The criterion for dependency files is "input file was read by
the linker".
The -t output omits archives from which no members were taken, input
linker scripts, -T linker scripts, version scripts, etc.  All of these
are important in dependency file output.
Alan Modra via Binutils June 15, 2020, 9:15 p.m. | #6
On Sun, Jun 14, 2020 at 11:22 AM Fangrui Song <i@maskray.me> wrote:
> >Please consider whether using Unordered_set (declared in system.h),

> >which uses a hashed set where supported, would be better than

> >std::set, which uses a binary tree. I'm OK either way, as long as

> >you've considered the two, though my guess would be that Unordered_set

> >would probably be better.

>

> std::unordered_set makes the output unstable across different systems.

> I like a data structure which iterates elements by insertion order (like JavaScript,Python,Ruby).

> This is even better than a std::set


Stable, deterministic output is an important invariant to maintain for
all build tools.
Deduplication here is not particularly important, I just did it since
it seemed easy.
To wit, the BFD implementation does not deduplicate, but just
preserves input order.

So I've just changed the gold implementation to use std::vector instead.
(The code is updated on the users/roland/ld-depfile branch.)


Thanks,
Roland
Alan Modra via Binutils June 15, 2020, 9:23 p.m. | #7
On Sun, Jun 14, 2020 at 11:33 PM Alan Modra <amodra@gmail.com> wrote:
> This should be moved later in the function, at least past the point

> where incompatible library versions are excluded.  I think I'd put it

> after the point where we exclude libc.so.1 being loaded if we've

> already loaded libc.so (when libc.so is a symlink to libc.so.1).


I disagree.  The criterion for inclusion in a dependency file is
simply that the file was read and its contents (or even mere presence)
might have influenced the work done.

I think the analogous case to an ELF file that was ignored because of
its contents (e.g. its e_machine value) is a C header file that is
entirely empty or has no tokens have preprocessing (e.g. its entire
contents where inside `#ifdef not_defined_for_this_machine`).  Such
source files are and must be included in dependency files, even though
they contributed nothing to the compilation directly.  If the contents
of that file changed, then repeating the compilation or link step that
reads that file may very well have different results.  The details of
what the compiler or linker saw or didn't see inside the file are
irrelevant to that fact.

The only exception is that files that were never opened at all because
they didn't exist or because a different file's existence preempted it
on a search path are not recorded as dependencies, even though
creating such a file in the future will indeed affect the results of
repeating the compilation or link step.  That's hard to avoid with the
way dependencies and search paths work, so we accept that lack.  It's
exactly the same as for -I paths in the compiler.


Thanks,
Roland
Fangrui Song June 15, 2020, 10:48 p.m. | #8
* Roland McGrath
>On Sun, Jun 14, 2020 at 11:33 PM Alan Modra <amodra@gmail.com> wrote:

>> This should be moved later in the function, at least past the point

>> where incompatible library versions are excluded.  I think I'd put it

>> after the point where we exclude libc.so.1 being loaded if we've

>> already loaded libc.so (when libc.so is a symlink to libc.so.1).

>

>I disagree.  The criterion for inclusion in a dependency file is

>simply that the file was read and its contents (or even mere presence)

>might have influenced the work done.

>

>I think the analogous case to an ELF file that was ignored because of

>its contents (e.g. its e_machine value) is a C header file that is

>entirely empty or has no tokens have preprocessing (e.g. its entire

>contents where inside `#ifdef not_defined_for_this_machine`).  Such

>source files are and must be included in dependency files, even though

>they contributed nothing to the compilation directly.  If the contents

>of that file changed, then repeating the compilation or link step that

>reads that file may very well have different results.  The details of

>what the compiler or linker saw or didn't see inside the file are

>irrelevant to that fact.

>

>The only exception is that files that were never opened at all because

>they didn't exist or because a different file's existence preempted it

>on a search path are not recorded as dependencies, even though

>creating such a file in the future will indeed affect the results of

>repeating the compilation or link step.  That's hard to avoid with the

>way dependencies and search paths work, so we accept that lack.  It's

>exactly the same as for -I paths in the compiler.


Yes. GNU ld and gold support skipping incompatible (e_machine,etc)
files. They should be listed. (LLD does not support the feature).

On 2020-06-15, Roland McGrath wrote:
>On Sat, Jun 13, 2020 at 9:44 AM Fangrui Song <i@maskray.me> wrote:

>> Sorry, but I am not convinced this option provides additional features.

>

>You are mistaken.

>

>> You'd want to link https://sourceware.org/bugzilla/show_bug.cgi?id=22843

>

>I hadn't noticed it had been filed.  I've updated the ChangeLog items

>to mention it and updated the bug.

>Thanks for the pointer!

>

>> tl;dr -t satisfies your needs: { echo -n 'a: '; clang a.o -o a -Wl,-t | sed 's/(.*//' | sort -u | sed '$!s/$/ \\/';} > a.d

>

>This is frankly ludicrous on its face, even if it were technically

>accurate (which it's not).

>

>The possibility of a ridiculous kludge is not a reason to avoid adding

>a sensible feature that is straightforward to use and fits existing

>models.

>

>A kludge like that is hopelessly impractical to integration into build

>systems, which is where this feature is useful.


I don't agree with your argument. If an option just transforms the
output of another one, I am not sure we want to support it.

That being said, this disagreement does not affect my acceptance of this
option, which I have acknowledged previously:

>This is a valid point. However, we still have a problem of lingering

>files in the dependency list, which will be flagged by the next make

>invocation.

>In the traditional make+cc model, -MP is needed

>(https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html )


>Moreover, it's not semantically equivalent.  While it's true that ld

>does print -t output on stdout (gold is incompatible in putting it on

>stderr), it's not true that -t output has equivalent information.

>

>The criterion for -t output is "input object file was included in the

>link".  The criterion for dependency files is "input file was read by

>the linker".

>The -t output omits archives from which no members were taken, input

>linker scripts, -T linker scripts, version scripts, etc.  All of these

>are important in dependency file output.


I agree with this point.  Please make sure the code is organized well so that a future
--reproduce can be easily implemented
(https://sourceware.org/bugzilla/show_bug.cgi?id=26119 I thought I filed
a feature request but it turns that I haven't)



Please ensure INCLUDE/INPUT/GROUP are handled. Please also consider how
to address the -MP problem.
Alan Modra via Binutils June 16, 2020, 2:01 a.m. | #9
On Mon, Jun 15, 2020 at 02:23:14PM -0700, Roland McGrath wrote:
> On Sun, Jun 14, 2020 at 11:33 PM Alan Modra <amodra@gmail.com> wrote:

> > This should be moved later in the function, at least past the point

> > where incompatible library versions are excluded.  I think I'd put it

> > after the point where we exclude libc.so.1 being loaded if we've

> > already loaded libc.so (when libc.so is a symlink to libc.so.1).

> 

> I disagree.  The criterion for inclusion in a dependency file is

> simply that the file was read and its contents (or even mere presence)

> might have influenced the work done.


Let's be clear on the files we are talking about here.  They are
shared libraries that are found in DT_NEEDED entries of other shared
libraries that are linked.  Some people have argued that ld should not
even be searching for these libraries, ie. that all dependencies
should be mentioned on the ld command line.  But since ld does search
for DT_NEEDED dependencies in directories given in /etc/ld.so.conf I
agree it is reasonable that shared libraries found and linked should
be a Makefile dependency.

I'm willing to concede that both a symlink and the file it points to
if found directly could be considered as dependencies.  I also agree
from a purist viewpoint that even libraries that can't be linked are
dependencies.  However I strongly disagree from a pragmatic view that
incompatible libraries should be included as dependencies.

Consider Joe Bloe, a long-time powerpc64 developer who has an older
machine that he's been using for years.  Joe has learned from
experience that reinstalling his operating system from scratch causes
problems.  Those old copies of gcc he kept for comparison purposes no
longer run, because they don't find older copies of libmpfr and so on.
So Joe doesn't reinstall, he just upgrades from time to time, keeping
old libraries.  His machine has *lots* of libraries, ELFv1 big-endian
as well as ELFv2 little-endian, ppc32 and so on, all available in
directories mentioned in /etc/ld.so.conf.  This all, amazingly, works
for Joe.  Joe hears about this nice new linker feature for
automatically generating Makefile dependencies, and makes use of it in
new ppc64 project he's working on.

Fred Bloggs downloads Joe's project and complains that he can't build
it.  Fred is running on a new install of the distro that Joe uses, has
the same compiler, libc and so on, so it ought to "just work".  After
some investigation Joe finds the Makefile has dependencies on ppc32
libraries.  "Weird", says Joe, "Those libraries can't possibly be
dependencies!"  Joe fires off a bug report.

Now how are you going to respond to Joe?  If you say those libraries
are indeed dependencies he's going to think the feature is useless for
him, and might even ask why you didn't make /etc/ld.so.conf a
dependency if you're so concerned about files that ld might touch.

> I think the analogous case to an ELF file that was ignored because of

> its contents (e.g. its e_machine value) is a C header file that is

> entirely empty or has no tokens have preprocessing (e.g. its entire

> contents where inside `#ifdef not_defined_for_this_machine`).  Such

> source files are and must be included in dependency files, even though

> they contributed nothing to the compilation directly.  If the contents

> of that file changed, then repeating the compilation or link step that

> reads that file may very well have different results.  The details of

> what the compiler or linker saw or didn't see inside the file are

> irrelevant to that fact.

> 

> The only exception is that files that were never opened at all because

> they didn't exist or because a different file's existence preempted it

> on a search path are not recorded as dependencies, even though

> creating such a file in the future will indeed affect the results of

> repeating the compilation or link step.  That's hard to avoid with the

> way dependencies and search paths work, so we accept that lack.  It's

> exactly the same as for -I paths in the compiler.

> 

> 

> Thanks,

> Roland


-- 
Alan Modra
Australia Development Lab, IBM
Alan Modra via Binutils June 19, 2020, 8:38 p.m. | #10
On Mon, Jun 15, 2020 at 7:01 PM Alan Modra <amodra@gmail.com> wrote:
> Let's be clear on the files we are talking about here.  They are

> shared libraries that are found in DT_NEEDED entries of other shared

> libraries that are linked.  Some people have argued that ld should not

> even be searching for these libraries, ie. that all dependencies

> should be mentioned on the ld command line.


I happen to be in that camp.  But I don't think it's relevant one way
or the other.
It's a fact of ld.bfd's behavior that it does read these files.
That's all that matters.
The criterion remains the same: all files the linker read while
producing that output.

> [...]  Joe hears about this nice new linker feature for

> automatically generating Makefile dependencies, and makes use of it in

> new ppc64 project he's working on.

>

> Fred Bloggs downloads Joe's project and complains that he can't build

> it.  Fred is running on a new install of the distro that Joe uses, has

> the same compiler, libc and so on, so it ought to "just work".  After

> some investigation Joe finds the Makefile has dependencies on ppc32

> libraries.  "Weird", says Joe, "Those libraries can't possibly be

> dependencies!"  Joe fires off a bug report.


I don't find this scenario motivating in the slightest.  Someone who uses
depfile contents to update a distributed makefile without thinking about
the difference between in-tree dependencies and system-installed dependencies
is just simply doing it wrong and is already breaking every person who uses
their code without running a precisely identical OS installation
(including whatever
messy history of past installs and upgrades).  We can certainly make clear that
the depfiles the linker produces are like compiler `-M` output and not
like `-MM` output:
they include all input files, regardless of whether they are part of
the system or toolchain
installation or not.  I am not proposing any linker feature analogous
to `-MM`, because
I don't think the linker has a sufficiently well-established and
useful criterion to distinguish
"system" from "local", as the compiler does for headers.not

I've updated the manual text thusly:

Write a @dfn{dependency file} to @var{depfile}.  This file contains a rule
suitable for @code{make} describing the output file and all the input files
that were read to produce it.  The output is similar to the compiler's
output with @samp{-M -MP} (@pxref{Preprocessor Options,, Options
Controlling the Preprocessor, gcc.info, Using the GNU Compiler
Collection}).  Note that there is no option like the compiler's @samp{-MM},
to exclude ``system files'' (which is not a well-specified concept in the
linker, unlike ``system headers'' in the compiler).  So the output from
@samp{--dependency-file} is always specific to the exact state of the
installation where it was produced, and should not be copied into
distributed makefiles without careful editing.

(I'm not on a connection I can use to update the sourceware branch
right now, but I'll do that later this afternoon.)


Thanks,
Roland
Alan Modra via Binutils June 22, 2020, 4:43 a.m. | #11
On Fri, Jun 19, 2020 at 01:38:44PM -0700, Roland McGrath wrote:
> On Mon, Jun 15, 2020 at 7:01 PM Alan Modra <amodra@gmail.com> wrote:

> > Let's be clear on the files we are talking about here.  They are

> > shared libraries that are found in DT_NEEDED entries of other shared

> > libraries that are linked.  Some people have argued that ld should not

> > even be searching for these libraries, ie. that all dependencies

> > should be mentioned on the ld command line.

> 

> I happen to be in that camp.


I also.

>  But I don't think it's relevant one way or the other.

> It's a fact of ld.bfd's behavior that it does read these files.

> That's all that matters.

> The criterion remains the same: all files the linker read while

> producing that output.


You do know that your patch doesn't meet that criterion, don't you?
Not that I'm asking you to meet your own criterion fully, that would
be ridiculous.

I just don't see the utility of reporting as dependencies shared
libraries that won't be loaded by the linker and that can't effect the
final output under anything like normal circumstances.

> I've updated the manual text thusly:


That's good.

-- 
Alan Modra
Australia Development Lab, IBM
Alan Modra via Binutils June 22, 2020, 11:44 p.m. | #12
On Sun, Jun 21, 2020 at 9:43 PM Alan Modra <amodra@gmail.com> wrote:
> > The criterion remains the same: all files the linker read while

> > producing that output.

>

> You do know that your patch doesn't meet that criterion, don't you?


I didn't.  That's the intent of the change.  I just noticed one case
I'd missed in the Gold implementation: the --section-ordering-file
file.  The new version of the branch covers that too.
Please tell me what input files I've missed in the BFD implementation,
or if there are any remaining omissions in the Gold implementation.

> Not that I'm asking you to meet your own criterion fully, that would

> be ridiculous.


It is not.  I want that asked of me.  The one class of exception I
intend but didn't explicitly state in the criterion is things like
gettext translations and /etc/localtime that we can say the linker
doesn't know it's accessing and linker logic to produce the output
file could never be affected by.

> I just don't see the utility of reporting as dependencies shared

> libraries that won't be loaded by the linker and that can't effect the

> final output under anything like normal circumstances.


I think there is clear utility and safety in keeping the criterion as
simple as possible: all files the linker read (modulo ).  If someone
has to exercise judgment about whether a file actually read by the
linker logic truly affected the final output, then there's always a
chance that judgment is in error.  The purpose of a dependency file is
to ensure incremental rebuilds happen when necessary, and false
positives in uncommon circumstances are harmless enough while any
false negative ever is potentially quite harmful to someone's
productivity.


Thanks,
Roland
Alan Modra via Binutils June 23, 2020, 3:11 p.m. | #13
I don't think we are going to come to any agreement on this argument,
but really the thing we're arguing about is a corner case.  After
trying the code out I expect people will likely complain about
duplicated dependencies before they hit the incompatible library
"dependencies".  So, I'll OK the ld.bfd part of the patch if you'll
own any bug reports on this ld feature.

Oh, and please fix some whitespace errors.  Add this to your
.git/config to see them.
[core]
	whitespace = indent-with-non-tab,space-before-tab,trailing-space

-- 
Alan Modra
Australia Development Lab, IBM
Alan Modra via Binutils June 23, 2020, 5:10 p.m. | #14
On Tue, Jun 23, 2020 at 8:11 AM Alan Modra via Binutils
<binutils@sourceware.org> wrote:
>

> I don't think we are going to come to any agreement on this argument,

> but really the thing we're arguing about is a corner case.  After

> trying the code out I expect people will likely complain about

> duplicated dependencies before they hit the incompatible library

> "dependencies".  So, I'll OK the ld.bfd part of the patch if you'll

> own any bug reports on this ld feature.


Does the incompatible library have any impact on linker output?
If not, will change in the incompatible library cause a relink? If yes,
it is a bug.

> Oh, and please fix some whitespace errors.  Add this to your

> .git/config to see them.

> [core]

>         whitespace = indent-with-non-tab,space-before-tab,trailing-space

>

> --

> Alan Modra

> Australia Development Lab, IBM




-- 
H.J.
Alan Modra via Binutils June 23, 2020, 7:15 p.m. | #15
On Tue, Jun 23, 2020 at 8:11 AM Alan Modra <amodra@gmail.com> wrote:
> [...] So, I'll OK the ld.bfd part of the patch if you'll

> own any bug reports on this ld feature.


Fair enough.

You indicated earlier there were input files that my change did not
include in dependency output.  If you think that's so, please point
them out to me.

> Oh, and please fix some whitespace errors.


Thanks for noticing.  I try to be conscientious about those but it's
always hard juggling the contrary requirements of various different
projects.

Committed to trunk.


Thanks,
Roland
Fangrui Song June 24, 2020, 4:32 p.m. | #16
* Alan Modra
>Oh, and please fix some whitespace errors.  Add this to your

>.git/config to see them.

>[core]

>	whitespace = indent-with-non-tab,space-before-tab,trailing-space


There probably should be a how-to-contribute page on
https://www.gnu.org/software/binutils/ and the tip should be mentioned there.

On 2020-06-23, Roland McGrath via Binutils wrote:
>On Tue, Jun 23, 2020 at 8:11 AM Alan Modra <amodra@gmail.com> wrote:

>> [...] So, I'll OK the ld.bfd part of the patch if you'll

>> own any bug reports on this ld feature.

>

>Fair enough.

>

>You indicated earlier there were input files that my change did not

>include in dependency output.  If you think that's so, please point

>them out to me.

>

>> Oh, and please fix some whitespace errors.

>

>Thanks for noticing.  I try to be conscientious about those but it's

>always hard juggling the contrary requirements of various different

>projects.

>

>Committed to trunk.

>

>

>Thanks,

>Roland


--- a/ld/lexsup.c
+++ b/ld/lexsup.c
@@ -111,6 +111,8 @@ static const struct ld_option ld_options[] =
      'c', N_("FILE"), N_("Read MRI format linker script"), TWO_DASHES },
    { {"dc", no_argument, NULL, 'd'},
      'd', NULL, N_("Force common symbols to be defined"), ONE_DASH },
+  { {"dependency-file", required_argument, NULL, OPTION_DEPENDENCY_FILE},
+    '\0', N_("FILE"), N_("Write dependency file"), TWO_DASHES },

Should have used EXACTLY_TWO_DASHES for newer options to avoid clash with short options.

gold/fileread.h gold/layout.cc gold/options.h ld/lexsup.c ld/ldmain.c and several other files
have formatting changes which should have been committed separately.
Alan Modra via Binutils June 24, 2020, 6:03 p.m. | #17
On Tue, Jun 23, 2020 at 12:17 PM Roland McGrath via Binutils
<binutils@sourceware.org> wrote:
>

> On Tue, Jun 23, 2020 at 8:11 AM Alan Modra <amodra@gmail.com> wrote:

> > [...] So, I'll OK the ld.bfd part of the patch if you'll

> > own any bug reports on this ld feature.

>

> Fair enough.

>

> You indicated earlier there were input files that my change did not

> include in dependency output.  If you think that's so, please point

> them out to me.

>

> > Oh, and please fix some whitespace errors.

>

> Thanks for noticing.  I try to be conscientious about those but it's

> always hard juggling the contrary requirements of various different

> projects.

>

> Committed to trunk.

>


I opened:

https://sourceware.org/bugzilla/show_bug.cgi?id=26169

-- 
H.J.
Alan Modra via Binutils June 24, 2020, 6:10 p.m. | #18
On Wed, Jun 24, 2020 at 11:03 AM H.J. Lu <hjl.tools@gmail.com> wrote:
>

> On Tue, Jun 23, 2020 at 12:17 PM Roland McGrath via Binutils

> <binutils@sourceware.org> wrote:

> >

> > On Tue, Jun 23, 2020 at 8:11 AM Alan Modra <amodra@gmail.com> wrote:

> > > [...] So, I'll OK the ld.bfd part of the patch if you'll

> > > own any bug reports on this ld feature.

> >

> > Fair enough.

> >

> > You indicated earlier there were input files that my change did not

> > include in dependency output.  If you think that's so, please point

> > them out to me.

> >

> > > Oh, and please fix some whitespace errors.

> >

> > Thanks for noticing.  I try to be conscientious about those but it's

> > always hard juggling the contrary requirements of various different

> > projects.

> >

> > Committed to trunk.

> >

>

> I opened:

>

> https://sourceware.org/bugzilla/show_bug.cgi?id=26169

>


There should be an entry in ld/NEWS.

-- 
H.J.

Patch

diff --git a/gold/fileread.cc b/gold/fileread.cc
index bebe0aba8d..ea89723168 100644
--- a/gold/fileread.cc
+++ b/gold/fileread.cc
@@ -124,6 +124,7 @@  static Initialize_lock
file_counts_initialize_lock(&file_counts_lock);
 unsigned long long File_read::total_mapped_bytes;
 unsigned long long File_read::current_mapped_bytes;
 unsigned long long File_read::maximum_mapped_bytes;
+std::set<std::string> File_read::files_read;

 // Class File_read::View.

@@ -211,6 +212,8 @@  File_read::open(const Task* task, const std::string& name)
       gold_debug(DEBUG_FILES, "Attempt to open %s succeeded",
  this->name_.c_str());
       this->token_.add_writer(task);
+      Hold_optional_lock hl(file_counts_lock);
+      File_read::files_read.insert(this->name_);
     }

   return this->descriptor_ >= 0;
@@ -1138,4 +1141,25 @@  Input_file::open_binary(const Task* task, const
std::string& name)
    binary_to_elf.converted_size());
 }

+void
+File_read::write_dependency_file(const char* dependency_file_name,
+                                 const char* output_file_name)
+{
+  FILE *depfile = fopen(dependency_file_name, "w");
+
+  fprintf(depfile, "%s:", output_file_name);
+  for (std::set<std::string>::const_iterator it = files_read.begin();
+       it != files_read.end();
+       ++it)
+    fprintf(depfile, " \\\n  %s", it->c_str());
+  fprintf(depfile, "\n");
+
+  for (std::set<std::string>::const_iterator it = files_read.begin();
+       it != files_read.end();
+       ++it)
+    fprintf(depfile, "\n%s:\n", it->c_str());
+
+  fclose(depfile);
+}
+
 } // End namespace gold.
diff --git a/gold/fileread.h b/gold/fileread.h
index cf92367c2c..005d2e28c8 100644
--- a/gold/fileread.h
+++ b/gold/fileread.h
@@ -27,6 +27,7 @@ 

 #include <list>
 #include <map>
+#include <set>
 #include <string>
 #include <vector>

@@ -207,6 +208,11 @@  class File_read
   static void
   print_stats();

+  // Write the dependency file listing all files read.
+  static void
+  write_dependency_file(const char* dependency_file_name,
+                        const char* output_file_name);
+
   // Return the open file descriptor (for plugins).
   int
   descriptor()
@@ -214,7 +220,7 @@  class File_read
     this->reopen_descriptor();
     return this->descriptor_;
   }
-
+
   // Return the file last modification time.  Calls gold_fatal if the stat
   // system call failed.
   Timespec
@@ -247,6 +253,9 @@  class File_read
   // --stats.
   static unsigned long long maximum_mapped_bytes;

+  // Set of names of all files read.
+  static std::set<std::string> files_read;
+
   // A view into the file.
   class View
   {
diff --git a/gold/layout.cc b/gold/layout.cc
index be437f3900..8510210868 100644
--- a/gold/layout.cc
+++ b/gold/layout.cc
@@ -6153,6 +6153,10 @@  Close_task_runner::run(Workqueue*, const Task*)
   if (this->options_->oformat_enum() != General_options::OBJECT_FORMAT_ELF)
     this->layout_->write_binary(this->of_);

+  if (this->options_->dependency_file())
+    File_read::write_dependency_file(this->options_->dependency_file(),
+                                     this->options_->output_file_name());
+
   this->of_->close();
 }

diff --git a/gold/options.h b/gold/options.h
index b2059d984c..eaf8727ba3 100644
--- a/gold/options.h
+++ b/gold/options.h
@@ -472,7 +472,7 @@  struct Struct_special : public Struct_var
   options::String_set::const_iterator   \
   varname__##_end() const   \
   { return this->varname__##_.value.end(); }                              \
-                                                                          \
+   \
   options::String_set::size_type                                          \
   varname__##_size() const                                                \
   { return this->varname__##_.value.size(); }                             \
@@ -800,6 +800,10 @@  class General_options
        N_("Do not demangle C++ symbols in log messages"),
        NULL);

+  DEFINE_string(dependency_file, options::TWO_DASHES, '\0', NULL,
+ N_("Write a dependency file listing all files read"),
+ N_("FILE"));
+
   DEFINE_bool(detect_odr_violations, options::TWO_DASHES, '\0', false,
        N_("Look for violations of the C++ One Definition Rule"),
        N_("Do not look for violations of the C++ One Definition Rule"));
diff --git a/ld/NEWS b/ld/NEWS
index 485e1cf5b9..f6ff062d0d 100644
--- a/ld/NEWS
+++ b/ld/NEWS
@@ -25,6 +25,10 @@ 
   searched relative to the directory of the linker script before other search
   paths.

+* Add command-line option --dependency-file to write a Make-style dependency
+  file listing the input files consulted by the linker, like the files written
+  by the compiler's -M -MP options.
+
 Changes in 2.34:

 * The ld check for "PHDR segment not covered by LOAD segment" is more
diff --git a/ld/ld.h b/ld/ld.h
index 71fd781267..1790dc81a6 100644
--- a/ld/ld.h
+++ b/ld/ld.h
@@ -286,6 +286,8 @@  typedef struct
   char *map_filename;
   FILE *map_file;

+  char *dependency_file;
+
   unsigned int split_by_reloc;
   bfd_size_type split_by_file;

diff --git a/ld/ld.texi b/ld/ld.texi
index bf474d4c62..dc22aee799 100644
--- a/ld/ld.texi
+++ b/ld/ld.texi
@@ -893,6 +893,15 @@  Use @var{output} as the name for the program
produced by @command{ld}; if this
 option is not specified, the name @file{a.out} is used by default.  The
 script command @code{OUTPUT} can also specify the output file name.

+@kindex --dependency-file=@var{depfile}
+@cindex dependency file
+@item --dependency-file=@var{depfile}
+Write a @dfn{dependency file} to @var{depfile}.  This file contains a rule
+suitable for @code{make} describing the output file and all the input files
+that were read to produce it.  The output is similar to the compiler's output
+with @samp{-M -MP} (@pxref{Preprocessor Options,, Options Controlling the
+Preprocessor, gcc.info, Using the GNU Compiler Collection}).
+
 @kindex -O @var{level}
 @cindex generating optimized output
 @item -O @var{level}
diff --git a/ld/ldelf.c b/ld/ldelf.c
index 8f2167e889..6fa09cfe6f 100644
--- a/ld/ldelf.c
+++ b/ld/ldelf.c
@@ -262,6 +262,8 @@  ldelf_try_needed (struct dt_needed *needed, int
force, int is_linux)
       return FALSE;
     }

+  track_dependency_files (name);
+
   /* Linker needs to decompress sections.  */
   abfd->flags |= BFD_DECOMPRESS;

@@ -1065,7 +1067,7 @@  ldelf_after_open (int use_libpath, int native,
int is_linux, int is_freebsd,
  }
       return;
     }
-
+
   if (!link_info.traditional_format)
     {
       bfd *elfbfd = NULL;
diff --git a/ld/ldfile.c b/ld/ldfile.c
index 60b28d3f0c..d5f10663eb 100644
--- a/ld/ldfile.c
+++ b/ld/ldfile.c
@@ -142,6 +142,8 @@  ldfile_try_open_bfd (const char *attempt,
       return FALSE;
     }

+  track_dependency_files (attempt);
+
   /* Linker needs to decompress sections.  */
   entry->the_bfd->flags |= BFD_DECOMPRESS;

diff --git a/ld/ldlex.h b/ld/ldlex.h
index 6388247b45..459b237aad 100644
--- a/ld/ldlex.h
+++ b/ld/ldlex.h
@@ -154,6 +154,7 @@  enum option_values
   OPTION_NO_PRINT_MAP_DISCARDED,
   OPTION_NON_CONTIGUOUS_REGIONS,
   OPTION_NON_CONTIGUOUS_REGIONS_WARNINGS,
+  OPTION_DEPENDENCY_FILE,
 };

 /* The initial parser states.  */
diff --git a/ld/ldmain.c b/ld/ldmain.c
index e2c559ea3e..8d777b55c4 100644
--- a/ld/ldmain.c
+++ b/ld/ldmain.c
@@ -159,6 +159,53 @@  static bfd_error_handler_type default_bfd_error_handler;

 struct bfd_link_info link_info;

+struct dependency_file
+{
+  struct dependency_file *next;
+  char *name;
+};
+
+static struct dependency_file *dependency_files, *dependency_files_tail;
+
+void
+track_dependency_files (const char *filename)
+{
+  struct dependency_file *dep
+    = (struct dependency_file *) xmalloc (sizeof (*dep));
+  dep->name = xstrdup (filename);
+  dep->next = NULL;
+  if (dependency_files == NULL)
+    dependency_files = dep;
+  else
+    dependency_files_tail->next = dep;
+  dependency_files_tail = dep;
+}
+
+static void
+write_dependency_file (void)
+{
+  FILE *out;
+  struct dependency_file *dep;
+
+  out = fopen (config.dependency_file, FOPEN_WT);
+  if (out == NULL)
+    {
+      einfo (_("%F%P: cannot open dependency file %s: %E\n"),
+             config.dependency_file);
+    }
+
+  fprintf (out, "%s:", output_filename);
+
+  for (dep = dependency_files; dep != NULL; dep = dep->next)
+    fprintf (out, " \\\n  %s", dep->name);
+
+  fprintf (out, "\n");
+  for (dep = dependency_files; dep != NULL; dep = dep->next)
+    fprintf (out, "\n%s:\n", dep->name);
+
+  fclose (out);
+}
+
 static void
 ld_cleanup (void)
 {
@@ -479,6 +526,9 @@  main (int argc, char **argv)
   ldexp_finish ();
   lang_finish ();

+  if (config.dependency_file != NULL)
+    write_dependency_file ();
+
   /* Even if we're producing relocatable output, some non-fatal errors should
      be reported in the exit status.  (What non-fatal errors, if any, do we
      want to ignore for relocatable output?)  */
diff --git a/ld/ldmain.h b/ld/ldmain.h
index 0f05821d1e..ac7db5720d 100644
--- a/ld/ldmain.h
+++ b/ld/ldmain.h
@@ -58,5 +58,6 @@  extern void add_ysym (const char *);
 extern void add_wrap (const char *);
 extern void add_ignoresym (struct bfd_link_info *, const char *);
 extern void add_keepsyms_file (const char *);
+extern void track_dependency_files (const char *);

 #endif
diff --git a/ld/lexsup.c b/ld/lexsup.c
index d84b334b34..f4e2ba1ee9 100644
--- a/ld/lexsup.c
+++ b/ld/lexsup.c
@@ -111,6 +111,8 @@  static const struct ld_option ld_options[] =
     'c', N_("FILE"), N_("Read MRI format linker script"), TWO_DASHES },
   { {"dc", no_argument, NULL, 'd'},
     'd', NULL, N_("Force common symbols to be defined"), ONE_DASH },
+  { {"dependency-file", required_argument, NULL, OPTION_DEPENDENCY_FILE},
+    '\0', N_("FILE"), N_("Write dependency file"), TWO_DASHES },
   { {"dp", no_argument, NULL, 'd'},
     '\0', NULL, NULL, ONE_DASH },
   { {"force-group-allocation", no_argument, NULL,
@@ -1631,6 +1633,10 @@  parse_args (unsigned argc, char **argv)
  case OPTION_PRINT_MAP_DISCARDED:
    config.print_map_discarded = TRUE;
    break;
+
+        case OPTION_DEPENDENCY_FILE:
+          config.dependency_file = optarg;
+          break;
  }
     }

diff --git a/ld/pe-dll.c b/ld/pe-dll.c
index f72b658341..3e8fe1be9b 100644
--- a/ld/pe-dll.c
+++ b/ld/pe-dll.c
@@ -3344,6 +3344,8 @@  pe_implied_import_dll (const char *filename)
       return FALSE;
     }

+  track_dependency_files (filename);
+
   /* PEI dlls seem to be bfd_objects.  */
   if (!bfd_check_format (dll, bfd_object))
     {