diff --git a/ld/pdb.c b/ld/pdb.c
index 6584ccd9911..6326e018666 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -76,6 +76,7 @@ struct type_entry
   struct type_entry *next;
   uint32_t index;
   uint32_t cv_hash;
+  bool has_udt_src_line;
   uint8_t data[];
 };
 
@@ -708,19 +709,19 @@ copy_filechksms (uint8_t *data, uint32_t size, char *string_table,
   return true;
 }
 
-/* Add a string to the strings table, if it's not already there.  */
-static void
+/* Add a string to the strings table, if it's not already there.  Returns its
+   offset within the string table.  */
+static uint32_t
 add_string (char *str, size_t len, struct string_table *strings)
 {
   uint32_t hash = calc_hash (str, len);
+  struct string *s;
   void **slot;
 
   slot = htab_find_slot_with_hash (strings->hashmap, str, hash, INSERT);
 
-  if (slot && !*slot)
+  if (!*slot)
     {
-      struct string *s;
-
       *slot = xmalloc (offsetof (struct string, s) + len);
 
       s = (struct string *) *slot;
@@ -741,6 +742,12 @@ add_string (char *str, size_t len, struct string_table *strings)
 
       strings->strings_len += len + 1;
     }
+  else
+    {
+      s = (struct string *) *slot;
+    }
+
+  return s->offset;
 }
 
 /* Return the hash of an entry in the string table.  */
@@ -1092,13 +1099,150 @@ is_name_anonymous (char *name, size_t len)
   return false;
 }
 
+/* Handle LF_UDT_SRC_LINE type entries, which are a special case.  These
+   give the source file and line number for each user-defined type that is
+   declared.  We parse these and emit instead an LF_UDT_MOD_SRC_LINE entry,
+   which also includes the module number.  */
+static bool
+handle_udt_src_line (uint8_t *data, uint16_t size, struct type_entry **map,
+		     uint32_t type_num, uint32_t num_types,
+		     struct types *ids, uint16_t mod_num,
+		     struct string_table *strings)
+{
+  struct lf_udt_src_line *usl = (struct lf_udt_src_line *) data;
+  uint32_t orig_type, source_file_type;
+  void **slot;
+  hashval_t hash;
+  struct type_entry *e, *type_e, *str_e;
+  struct lf_udt_mod_src_line *umsl;
+  struct lf_string_id *str;
+  uint32_t source_file_offset;
+
+  if (size < sizeof (struct lf_udt_src_line))
+    {
+      einfo (_("%P: warning: truncated CodeView type record"
+		" LF_UDT_SRC_LINE\n"));
+      return false;
+    }
+
+  /* Check if LF_UDT_MOD_SRC_LINE already present for type, and return.  */
+
+  orig_type = bfd_getl32 (&usl->type);
+
+  if (orig_type < TPI_FIRST_INDEX ||
+      orig_type >= TPI_FIRST_INDEX + num_types ||
+      !map[orig_type - TPI_FIRST_INDEX])
+    {
+      einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
+	       " referred to unknown type %v\n"), orig_type);
+      return false;
+    }
+
+  type_e = map[orig_type - TPI_FIRST_INDEX];
+
+  /* Skip if type already declared in other module.  */
+  if (type_e->has_udt_src_line)
+    return true;
+
+  if (!remap_type (&usl->type, map, type_num, num_types))
+    return false;
+
+  /* Extract string from source_file_type.  */
+
+  source_file_type = bfd_getl32 (&usl->source_file_type);
+
+  if (source_file_type < TPI_FIRST_INDEX ||
+      source_file_type >= TPI_FIRST_INDEX + num_types ||
+      !map[source_file_type - TPI_FIRST_INDEX])
+    {
+      einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
+	       " referred to unknown string %v\n"), source_file_type);
+      return false;
+    }
+
+  str_e = map[source_file_type - TPI_FIRST_INDEX];
+
+  if (bfd_getl16 (str_e->data + sizeof (uint16_t)) != LF_STRING_ID)
+    {
+      einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
+	       " pointed to unexpected record type\n"));
+      return false;
+    }
+
+  str = (struct lf_string_id *) str_e->data;
+
+  /* Add string to string table.  */
+
+  source_file_offset = add_string (str->string, strlen (str->string),
+				   strings);
+
+  /* Add LF_UDT_MOD_SRC_LINE entry.  */
+
+  size = sizeof (struct lf_udt_mod_src_line);
+
+  e = xmalloc (offsetof (struct type_entry, data) + size);
+
+  e->next = NULL;
+  e->index = ids->num_types;
+  e->has_udt_src_line = false;
+
+  /* LF_UDT_MOD_SRC_LINE use calc_hash on the type number, rather than
+     the crc32 used for type hashes elsewhere.  */
+  e->cv_hash = calc_hash ((char *) &usl->type, sizeof (uint32_t));
+
+  type_e->has_udt_src_line = true;
+
+  umsl = (struct lf_udt_mod_src_line *) e->data;
+
+  bfd_putl16 (size - sizeof (uint16_t), &umsl->size);
+  bfd_putl16 (LF_UDT_MOD_SRC_LINE, &umsl->kind);
+  memcpy (&umsl->type, &usl->type, sizeof (uint32_t));
+  bfd_putl32 (source_file_offset,
+	      &umsl->source_file_string);
+  memcpy (&umsl->line_no, &usl->line_no, sizeof (uint16_t));
+  bfd_putl16 (mod_num + 1, &umsl->module_no);
+
+  hash = iterative_hash (e->data, size, 0);
+
+  slot = htab_find_slot_with_hash (ids->hashmap, data, hash, INSERT);
+  if (!slot)
+    {
+      free (e);
+      return false;
+    }
+
+  if (*slot)
+    {
+      free (e);
+      einfo (_("%P: warning: duplicate CodeView type record "
+	       "LF_UDT_MOD_SRC_LINE\n"));
+      return false;
+    }
+
+  *slot = e;
+
+  if (ids->last)
+    ids->last->next = e;
+  else
+    ids->first = e;
+
+  ids->last = e;
+
+  map[type_num] = e;
+
+  ids->num_types++;
+
+  return true;
+}
+
 /* Parse a type definition in the .debug$T section.  We remap the numbers
    of any referenced types, and if the type is not a duplicate of one
    already seen add it to types (for TPI types) or ids (for IPI types).  */
 static bool
 handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
 	     uint32_t num_types, struct types *types,
-	     struct types *ids)
+	     struct types *ids, uint16_t mod_num,
+	     struct string_table *strings)
 {
   uint16_t size, type;
   void **slot;
@@ -2069,6 +2213,10 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
 	break;
       }
 
+    case LF_UDT_SRC_LINE:
+      return handle_udt_src_line (data, size, map, type_num, num_types,
+				  ids, mod_num, strings);
+
     default:
       einfo (_("%P: warning: unrecognized CodeView type %v\n"), type);
       return false;
@@ -2098,6 +2246,8 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
       else
 	e->cv_hash = crc32 (data, size);
 
+      e->has_udt_src_line = false;
+
       memcpy (e->data, data, size);
 
       if (t->last)
@@ -2123,7 +2273,8 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
    found to handle_type.  */
 static bool
 handle_debugt_section (asection *s, bfd *mod, struct types *types,
-		       struct types *ids)
+		       struct types *ids, uint16_t mod_num,
+		       struct string_table *strings)
 {
   bfd_byte *data = NULL;
   size_t off;
@@ -2180,7 +2331,8 @@ handle_debugt_section (asection *s, bfd *mod, struct types *types,
 
       size = bfd_getl16 (data + off);
 
-      if (!handle_type (data + off, map, type_num, num_types, types, ids))
+      if (!handle_type (data + off, map, type_num, num_types, types, ids,
+			mod_num, strings))
 	{
 	  free (data);
 	  free (map);
@@ -2206,7 +2358,7 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 			uint32_t *c13_info_size,
 			struct mod_source_files *mod_source,
 			bfd *abfd, struct types *types,
-			struct types *ids)
+			struct types *ids, uint16_t mod_num)
 {
   uint8_t int_buf[sizeof (uint32_t)];
   uint8_t *c13_info = NULL;
@@ -2230,7 +2382,7 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 	}
       else if (!strcmp (s->name, ".debug$T") && s->size >= sizeof (uint32_t))
 	{
-	  if (!handle_debugt_section (s, mod, types, ids))
+	  if (!handle_debugt_section (s, mod, types, ids, mod_num, strings))
 	    {
 	      free (c13_info);
 	      free (mod_source->files);
@@ -2365,7 +2517,7 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
       if (!populate_module_stream (stream, in, &sym_byte_size,
 				   strings, &c13_info_size,
 				   &source->mods[mod_num], abfd,
-				   types, ids))
+				   types, ids, mod_num))
 	{
 	  for (unsigned int i = 0; i < source->mod_count; i++)
 	    {
diff --git a/ld/pdb.h b/ld/pdb.h
index 7d87a3fef07..668f3e168fd 100644
--- a/ld/pdb.h
+++ b/ld/pdb.h
@@ -59,6 +59,8 @@
 #define LF_BUILDINFO			0x1603
 #define LF_SUBSTR_LIST			0x1604
 #define LF_STRING_ID			0x1605
+#define LF_UDT_SRC_LINE			0x1606
+#define LF_UDT_MOD_SRC_LINE		0x1607
 
 #define LF_CHAR				0x8000
 #define LF_SHORT			0x8001
@@ -517,6 +519,27 @@ struct lf_mfunc_id
   char name[];
 } ATTRIBUTE_PACKED;
 
+/* lfUdtSrcLine in cvinfo.h */
+struct lf_udt_src_line
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t type;
+  uint32_t source_file_type;
+  uint32_t line_no;
+} ATTRIBUTE_PACKED;
+
+/* lfUdtModSrcLine in cvinfo.h */
+struct lf_udt_mod_src_line
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t type;
+  uint32_t source_file_string;
+  uint32_t line_no;
+  uint16_t module_no;
+} ATTRIBUTE_PACKED;
+
 extern bool create_pdb_file (bfd *, const char *, const unsigned char *);
 
 #endif
diff --git a/ld/testsuite/ld-pe/pdb-types3-hashlist.d b/ld/testsuite/ld-pe/pdb-types3-hashlist.d
new file mode 100644
index 00000000000..4a3775be42a
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types3-hashlist.d
@@ -0,0 +1,5 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 d4d90000 0c1c0000                    *
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types3-skiplist.d b/ld/testsuite/ld-pe/pdb-types3-skiplist.d
new file mode 100644
index 00000000000..52c10fa501d
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types3-skiplist.d
@@ -0,0 +1,5 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 00100000 00000000                    *
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types3-typelist.d b/ld/testsuite/ld-pe/pdb-types3-typelist.d
new file mode 100644
index 00000000000..d6ffaadb246
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types3-typelist.d
@@ -0,0 +1,7 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 0e000516 00000000 666f6f2e 6800f2f1  ........foo.h...
+ 0010 10000716 01100000 01000000 2a000000  ............*...
+ 0020 0100                                 ..              
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types3a.s b/ld/testsuite/ld-pe/pdb-types3a.s
new file mode 100644
index 00000000000..def001e52f7
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types3a.s
@@ -0,0 +1,57 @@
+.equ CV_SIGNATURE_C13, 4
+.equ T_INT4, 0x0074
+
+.equ LF_FIELDLIST, 0x1203
+.equ LF_STRUCTURE, 0x1505
+.equ LF_MEMBER, 0x150d
+.equ LF_STRING_ID, 0x1605
+.equ LF_UDT_SRC_LINE, 0x1606
+
+.section ".debug$T", "rn"
+
+.long CV_SIGNATURE_C13
+
+# Type 1000, fieldlist for struct foo
+.fieldlist1:
+.short .struct1 - .fieldlist1 - 2
+.short LF_FIELDLIST
+.short LF_MEMBER
+.short 3 # public
+.long T_INT4
+.short 0 # offset
+.asciz "num"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1001, struct foo
+.struct1:
+.short .string1 - .struct1 - 2
+.short LF_STRUCTURE
+.short 1 # no. members
+.short 0 # property
+.long 0x1000 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 4 # size
+.asciz "foo" # name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1002, string "foo"
+.string1:
+.short .udtsrcline1 - .string1 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "foo.h"
+.byte 0xf2
+.byte 0xf1
+
+# Type 1003, UDT source line for type 1001
+.udtsrcline1:
+.short .types_end - .udtsrcline1 - 2
+.short LF_UDT_SRC_LINE
+.long 0x1001
+.long 0x1002 # source file string
+.long 42 # line no.
+
+.types_end:
diff --git a/ld/testsuite/ld-pe/pdb-types3b.s b/ld/testsuite/ld-pe/pdb-types3b.s
new file mode 100644
index 00000000000..a22234e221f
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types3b.s
@@ -0,0 +1,68 @@
+.equ CV_SIGNATURE_C13, 4
+
+.equ T_LONG, 0x0012
+.equ T_INT4, 0x0074
+
+.equ LF_MODIFIER, 0x1001
+.equ LF_FIELDLIST, 0x1203
+.equ LF_STRUCTURE, 0x1505
+.equ LF_MEMBER, 0x150d
+.equ LF_STRING_ID, 0x1605
+.equ LF_UDT_SRC_LINE, 0x1606
+
+.section ".debug$T", "rn"
+
+.long CV_SIGNATURE_C13
+
+# Type 1000, const long
+.mod1:
+.short .fieldlist1 - .mod1 - 2
+.short LF_MODIFIER
+.long T_LONG
+.short 1 # const
+.short 0 # padding
+
+# Type 1001, fieldlist for struct foo
+.fieldlist1:
+.short .struct1 - .fieldlist1 - 2
+.short LF_FIELDLIST
+.short LF_MEMBER
+.short 3 # public
+.long T_INT4
+.short 0 # offset
+.asciz "num"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1002, struct foo
+.struct1:
+.short .string1 - .struct1 - 2
+.short LF_STRUCTURE
+.short 1 # no. members
+.short 0 # property
+.long 0x1001 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 4 # size
+.asciz "foo" # name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1003, string "foo"
+.string1:
+.short .udtsrcline1 - .string1 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "foo.h"
+.byte 0xf2
+.byte 0xf1
+
+# Type 1004, UDT source line for type 1002
+.udtsrcline1:
+.short .types_end - .udtsrcline1 - 2
+.short LF_UDT_SRC_LINE
+.long 0x1002
+.long 0x1003 # source file string
+.long 42 # line no.
+
+.types_end:
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
index 9753216f544..1a5416890d1 100644
--- a/ld/testsuite/ld-pe/pdb.exp
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -1319,9 +1319,142 @@ proc test6 { } {
     }
 }
 
+proc test7 { } {
+    global as
+    global ar
+    global ld
+    global objdump
+    global srcdir
+    global subdir
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-types3a.s tmpdir/pdb-types3a.o] {
+	unsupported "Build pdb-types3a.o"
+	return
+    }
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-types3b.s tmpdir/pdb-types3b.o] {
+	unsupported "Build pdb-types3b.o"
+	return
+    }
+
+    if ![ld_link $ld "tmpdir/pdb-types3.exe" "--pdb=tmpdir/pdb-types3.pdb tmpdir/pdb-types3a.o tmpdir/pdb-types3b.o"] {
+	unsupported "Create PE image with PDB file"
+	return
+    }
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types3.pdb 0004"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract IPI stream"
+	return
+    } else {
+	pass "Extracted IPI stream"
+    }
+
+    set fi [open tmpdir/0004]
+    fconfigure $fi -translation binary
+
+    seek $fi 16 current
+
+    set data [read $fi 4]
+    binary scan $data i type_list_size
+
+    set data [read $fi 2]
+    binary scan $data s hash_stream_index
+
+    seek $fi 10 current
+
+    set data [read $fi 4]
+    binary scan $data i hash_list_offset
+
+    set data [read $fi 4]
+    binary scan $data i hash_list_size
+
+    set data [read $fi 4]
+    binary scan $data i skip_list_offset
+
+    set data [read $fi 4]
+    binary scan $data i skip_list_size
+
+    seek $fi 8 current
+
+    set type_list [read $fi $type_list_size]
+
+    close $fi
+
+    set fi [open tmpdir/pdb-types3-typelist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $type_list
+    close $fi
+
+    # check type list
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types3-typelist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types3-typelist"]
+    if ![string match $exp $got] {
+	fail "Incorrect type list in IPI stream."
+    } else {
+	pass "Correct type list in IPI stream."
+    }
+
+    # extract hash list and skip list
+
+    set index_str [format "%04x" $hash_stream_index]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types3.pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract IPI hash stream."
+    } else {
+	pass "Extracted IPI hash stream."
+    }
+
+    set fi [open tmpdir/$index_str]
+    fconfigure $fi -translation binary
+
+    seek $fi $hash_list_offset
+    set hash_list [read $fi $hash_list_size]
+
+    seek $fi $skip_list_offset
+    set skip_list [read $fi $skip_list_size]
+
+    close $fi
+
+    # check hash list
+
+    set fi [open tmpdir/pdb-types3-hashlist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $hash_list
+    close $fi
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types3-hashlist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types3-hashlist"]
+    if ![string match $exp $got] {
+	fail "Incorrect hash list in IPI stream."
+    } else {
+	pass "Correct hash list in IPI stream."
+    }
+
+    # check skip list
+
+    set fi [open tmpdir/pdb-types3-skiplist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $skip_list
+    close $fi
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types3-skiplist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types3-skiplist"]
+    if ![string match $exp $got] {
+	fail "Incorrect skip list in IPI stream."
+    } else {
+	pass "Correct skip list in IPI stream."
+    }
+}
+
 test1
 test2
 test3
 test4
 test5
 test6
+test7
