[v3] binutils/dwarf: Print embedded source, when available

Message ID 20260421020204.2759264-1-hawkinsw@obs.cr
State New
Headers
Series [v3] binutils/dwarf: Print embedded source, when available |

Commit Message

Will Hawkins April 21, 2026, 2:02 a.m. UTC
  v3 should contain a test that passes on aarch64 as well as x86_64. Sorry
for the hassle!

Hope everyone had a great Monday!

Will

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
---
 v3: Fixed failing test on aarch64.
 v2: Fix variable scoping, name of line number header entry; add test.

 binutils/dwarf.c                              |  34 ++-
 .../binutils-all/dwarf-embedded-source.S      | 240 ++++++++++++++++++
 .../dwarf-embedded-source.rawline             |  13 +
 binutils/testsuite/binutils-all/objdump.exp   |  24 ++
 include/dwarf2.h                              |   2 +
 5 files changed, 308 insertions(+), 5 deletions(-)
 create mode 100644 binutils/testsuite/binutils-all/dwarf-embedded-source.S
 create mode 100644 binutils/testsuite/binutils-all/dwarf-embedded-source.rawline
  

Comments

Jens Remus April 21, 2026, 9:06 a.m. UTC | #1
On 4/21/2026 4:02 AM, Will Hawkins wrote:
> v3 should contain a test that passes on aarch64 as well as x86_64. Sorry
> for the hassle!
> 
> Hope everyone had a great Monday!
> 
> Will

Hello Will, it would be helpful if you could keep the original commit
message from v1 of your patch (except for the last two paragraphs),
including the description and motivation, and place any additional
comments below the three dashes (as you did with the patch changelog).
This should make it easier for everyone to clearly see what is intended
to be committed without needing to dig through previous patch versions.

> 
> Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
> ---
>  v3: Fixed failing test on aarch64.
>  v2: Fix variable scoping, name of line number header entry; add test.
> 
>  binutils/dwarf.c                              |  34 ++-
>  .../binutils-all/dwarf-embedded-source.S      | 240 ++++++++++++++++++
>  .../dwarf-embedded-source.rawline             |  13 +
>  binutils/testsuite/binutils-all/objdump.exp   |  24 ++
>  include/dwarf2.h                              |   2 +
>  5 files changed, 308 insertions(+), 5 deletions(-)
>  create mode 100644 binutils/testsuite/binutils-all/dwarf-embedded-source.S
>  create mode 100644 binutils/testsuite/binutils-all/dwarf-embedded-source.rawline
Thanks and regards,
Jens
  
Jan Beulich April 21, 2026, 4:03 p.m. UTC | #2
On 21.04.2026 04:02, Will Hawkins wrote:
> v3 should contain a test that passes on aarch64 as well as x86_64. Sorry
> for the hassle!

What about other targets, including 32-bit ones?

> --- a/binutils/dwarf.c
> +++ b/binutils/dwarf.c
> @@ -5847,6 +5847,10 @@ display_formatted_table (unsigned char *data,
>  	      case DW_LNCT_MD5:
>  		printf (_("\tMD5\t\t\t"));
>  		break;
> +	      case DW_LNCT_LLVM_source:
> +	      case DW_LNCT_source:
> +		/* Skip source ... display on next line.  */
> +		break;

Just keep recorded that I'm concerned of such unqualified uses of constants
from custom ranges (LLVM in this case).

> @@ -5861,8 +5865,10 @@ display_formatted_table (unsigned char *data,
>        unsigned char *datapass = data;
>  
>        printf ("  %d", last_entry++);
> -      /* Delay displaying name as the last entry for better screen layout.  */
> -      for (namepass = 0; namepass < 2; namepass++)
> +      /* Delay displaying name/source as the last entry for better screen
> +	 layout.  */
> +      int namesourcepass;
> +      for (namesourcepass = 0; namesourcepass < 3; namesourcepass++)

      for (unsigned int namesourcepass = 0; namesourcepass < 3; namesourcepass++)

(suitably line wrapped if necessary)

> @@ -5872,13 +5878,31 @@ display_formatted_table (unsigned char *data,
>  
>  	      READ_ULEB (content_type, format, end);
>  	      READ_ULEB (form, format, end);
> -	      bool do_loc = (content_type == DW_LNCT_path) != (namepass == 1);
> +
> +	      bool do_loc = (content_type == DW_LNCT_path)
> +		!= (namesourcepass == 1);
> +	      do_loc |= (content_type == DW_LNCT_LLVM_source
> +		  || content_type == DW_LNCT_source)
> +		!= (namesourcepass == 2);

Indentation is better now that it (iirc) was in v1, but it's still not
quite right. Binary operators wrapped to the next line want to align
with the corresponding part of the expression on the earlier line. At
the example ...

> +	      char delimiter = '\t';
> +
> +	      /* Print Source last (if available) and print it
> +		 starting on the next line.  */
> +	      if (namesourcepass == 2 && (content_type == DW_LNCT_LLVM_source
> +		    || content_type == DW_LNCT_source))

... here:

	      if (namesourcepass == 2
		  && (content_type == DW_LNCT_LLVM_source
		      || content_type == DW_LNCT_source))

> --- /dev/null
> +++ b/binutils/testsuite/binutils-all/dwarf-embedded-source.S
> @@ -0,0 +1,240 @@
> +	.text
> +	.file	"small.c"
> +	.globl	main
> +	.p2align	4, 0x90
> +	.type	main,@function
> +main:
> +.Lbegin_func_main:
> +.Lend_func_main:
> +	.size	main, .Lend_func_main-main
> +	.section	.debug_abbrev,"",@progbits
> +	.byte	1                               /* Abbreviation Code */
> +	.byte	17                              /* DW_TAG_compile_unit */
> +	.byte	1                               /* DW_CHILDREN_yes */
> +	.byte	37                              /* DW_AT_producer */
> +	.byte	37                              /* DW_FORM_strx1 */
> +	.byte	19                              /* DW_AT_language */
> +	.byte	5                               /* DW_FORM_data2 */
> +	.byte	3                               /* DW_AT_name */
> +	.byte	37                              /* DW_FORM_strx1 */
> +	.byte	114                             /* DW_AT_str_offsets_base */
> +	.byte	23                              /* DW_FORM_sec_offset */
> +	.byte	16                              /* DW_AT_stmt_list */
> +	.byte	23                              /* DW_FORM_sec_offset */
> +	.byte	27                              /* DW_AT_comp_dir */
> +	.byte	37                              /* DW_FORM_strx1 */
> +	.byte	17                              /* DW_AT_low_pc */
> +	.byte	27                              /* DW_FORM_addrx */
> +	.byte	18                              /* DW_AT_high_pc */
> +	.byte	6                               /* DW_FORM_data4 */
> +	.byte	115                             /* DW_AT_addr_base */
> +	.byte	23                              /* DW_FORM_sec_offset */
> +	.byte	0                               /* EOM(1) */
> +	.byte	0                               /* EOM(2) */
> +	.byte	2                               /* Abbreviation Code */
> +	.byte	46                              /* DW_TAG_subprogram */
> +	.byte	0                               /* DW_CHILDREN_no */
> +	.byte	17                              /* DW_AT_low_pc */
> +	.byte	27                              /* DW_FORM_addrx */
> +	.byte	18                              /* DW_AT_high_pc */
> +	.byte	6                               /* DW_FORM_data4 */
> +	.byte	64                              /* DW_AT_frame_base */
> +	.byte	24                              /* DW_FORM_exprloc */
> +	.byte	3                               /* DW_AT_name */
> +	.byte	37                              /* DW_FORM_strx1 */
> +	.byte	58                              /* DW_AT_decl_file */
> +	.byte	11                              /* DW_FORM_data1 */
> +	.byte	59                              /* DW_AT_decl_line */
> +	.byte	11                              /* DW_FORM_data1 */
> +	.byte	73                              /* DW_AT_type */
> +	.byte	19                              /* DW_FORM_ref4 */
> +	.byte	63                              /* DW_AT_external */
> +	.byte	25                              /* DW_FORM_flag_present */
> +	.byte	0                               /* EOM(1) */
> +	.byte	0                               /* EOM(2) */
> +	.byte	3                               /* Abbreviation Code */
> +	.byte	36                              /* DW_TAG_base_type */
> +	.byte	0                               /* DW_CHILDREN_no */
> +	.byte	3                               /* DW_AT_name */
> +	.byte	37                              /* DW_FORM_strx1 */
> +	.byte	62                              /* DW_AT_encoding */
> +	.byte	11                              /* DW_FORM_data1 */
> +	.byte	11                              /* DW_AT_byte_size */
> +	.byte	11                              /* DW_FORM_data1 */
> +	.byte	0                               /* EOM(1) */
> +	.byte	0                               /* EOM(2) */
> +	.byte	0                               /* EOM(3) */
> +	.section	.debug_info,"",@progbits
> +.Lcu_begin0:
> +	.long	.Ldebug_info_end0-.Ldebug_info_start0 /* Length of Unit */
> +.Ldebug_info_start0:
> +	.short	5                               /* DWARF version number */
> +	.byte	1                               /* DWARF Unit Type */
> +	.byte	8                               /* Address Size (in bytes) */
> +	.long	.debug_abbrev                   /* Offset Into Abbrev. Section */
> +	.byte	1                               /* Abbrev [1] 0xc:0x2b DW_TAG_compile_unit */
> +	.byte	0                               /* DW_AT_producer */
> +	.short	29                              /* DW_AT_language */
> +	.byte	1                               /* DW_AT_name */
> +	.long	.Lstr_offsets_base0             /* DW_AT_str_offsets_base */
> +	.long	.Lline_table_start0             /* DW_AT_stmt_list */
> +	.byte	2                               /* DW_AT_comp_dir */
> +	.byte	0                               /* DW_AT_low_pc */
> +	.long	.Lend_func_main-.Lbegin_func_main   /* DW_AT_high_pc */
> +	.long	.Laddr_table_base0              /* DW_AT_addr_base */
> +	.byte	2                               /* Abbrev [2] 0x23:0xf DW_TAG_subprogram */
> +	.byte	0                               /* DW_AT_low_pc */
> +	.long	.Lend_func_main-.Lbegin_func_main   /* DW_AT_high_pc */
> +	.byte	1                               /* DW_AT_frame_base */
> +	.byte	86
> +	.byte	3                               /* DW_AT_name */
> +	.byte	0                               /* DW_AT_decl_file */
> +	.byte	1                               /* DW_AT_decl_line */
> +	.long	50                              /* DW_AT_type */
> +                                                /* DW_AT_external */
> +	.byte	3                               /* Abbrev [3] 0x32:0x4 DW_TAG_base_type */
> +	.byte	4                               /* DW_AT_name */
> +	.byte	5                               /* DW_AT_encoding */
> +	.byte	4                               /* DW_AT_byte_size */
> +	.byte	0                               /* End Of Children Mark */
> +.Ldebug_info_end0:
> +	.section	.debug_str_offsets,"",@progbits
> +	.long	24                              /* Length of String Offsets Set */
> +	.short	5
> +	.short	0
> +.Lstr_offsets_base0:
> +	.section	.debug_str,"MS",@progbits,1
> +.Linfo_string0:
> +	.asciz	"clang (with hand edits)"       /* string offset=0 */
> +.Linfo_string1:
> +	.asciz	"small.c"                       /* string offset=44 */
> +.Linfo_string2:
> +	.asciz	"/path/to/code/"
> +.Linfo_string3:
> +	.asciz	"main"                          /* string offset=77 */
> +.Linfo_string4:
> +	.asciz	"int"                           /* string offset=82 */
> +	.section	.debug_str_offsets,"",@progbits
> +	.long	.Linfo_string0
> +	.long	.Linfo_string1
> +	.long	.Linfo_string2
> +	.long	.Linfo_string3
> +	.long	.Linfo_string4
> +	.section	.debug_line_str,"MS",@progbits,1
> +.Lline_string1:
> +	.asciz	"/path/to/code/"                /* string offset=0 */
> +.Lline_string2:
> +	.asciz	"small.c"                       /* string offset=15 */
> +.Lline_string3:
> +	.asciz	"int main() {\n  return 0;\n}\n"/* string offset=23 */
> +	.section	.debug_addr,"",@progbits
> +	.long	.Ldebug_addr_end0-.Ldebug_addr_start0 /* Length of contribution */
> +.Ldebug_addr_start0:
> +	.short	5                               /* DWARF version number */
> +	.byte	8                               /* Address size */
> +	.byte	0                               /* Segment selector size */
> +.Laddr_table_base0:
> +	.quad	.Lbegin_func_main
> +.Ldebug_addr_end0:

Up to here the comments allow to reasonably follow what is there.

> +	.section	.debug_line,"",@progbits
> +.Lline_table_start0:
> +	.byte 0x60
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x05
> +	.byte 0x00
> +	.byte 0x08
> +	.byte 0x00
> +	.byte 0x3E
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x01
> +	.byte 0x01
> +	.byte 0x01
> +	.byte 0xfb
> +	.byte 0x0e
> +	.byte 0x0d
> +	.byte 0x00
> +	.byte 0x01
> +	.byte 0x01
> +	.byte 0x01
> +	.byte 0x01
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x01
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x01
> +	.byte 0x01
> +	.byte 0x01
> +	.byte 0x1f
> +	.byte 0x01
> +	.byte 0x00                            /* Offset to directory. */
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x04
> +	.byte 0x01
> +	.byte 0x1f
> +	.byte 0x02
> +	.byte 0x0f                            /* Offset to filename. */
> +	.byte 0x05
> +	.byte 0x1e
> +	.byte 0x81
> +	.byte 0x40
> +	.byte 0x1f
> +	.byte 0x01
> +	.byte 0x0f
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x03
> +	.byte 0xad
> +	.byte 0x19
> +	.byte 0x9b
> +	.byte 0xfa
> +	.byte 0x21
> +	.byte 0x80
> +	.byte 0x26
> +	.byte 0xd0
> +	.byte 0xf1
> +	.byte 0xbe
> +	.byte 0x37
> +	.byte 0x41
> +	.byte 0x65
> +	.byte 0xa1
> +	.byte 0x6d
> +	.byte 0x17                            /* Offset to source code. */
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x04
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x09
> +	.byte 0x02
> +	.byte 0x10
> +	.byte 0x11
> +	.byte 0x40
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x00
> +	.byte 0x01
> +	.byte 0x05
> +	.byte 0x05
> +	.byte 0x0a
> +	.byte 0xad
> +	.byte 0x06
> +	.byte 0x0b
> +	.byte 0x2e
> +	.byte 0x02
> +	.byte 0x02
> +	.byte 0x00
> +	.byte 0x01
> +	.byte 0x01

The same cannot be said here, which makes this pretty hard to maintain (i.e.
change if need be). Question is anyway - do we really need to resort to
.byte to express what's wanted?

> --- /dev/null
> +++ b/binutils/testsuite/binutils-all/dwarf-embedded-source.rawline
> @@ -0,0 +1,13 @@
> +#...
> + The Directory Table \(offset 0x22, lines 1, columns 1\):
> +
> +  Entry	Name
> +  0	\(indirect line string, offset: 0\): \/path\/to\/code\/
> +
> + The File Name Table \(offset 0x31, lines 1, columns 4\):
> +  Entry	Dir	MD5				Name
> +  0	0 0x6da1654137bef1d0268021fa9b19ad03	\(indirect line string, offset: 0xf\): small.c
> +  Source: \(indirect line string, offset: 0x17\): int main\(\) {
> +  return 0;
> +}

The figure braces would better also be escaped, I think.

> --- a/binutils/testsuite/binutils-all/objdump.exp
> +++ b/binutils/testsuite/binutils-all/objdump.exp
> @@ -615,6 +615,30 @@ if { ![is_elf_format] } then {
>      file_on_host delete $output
>  }
>  
> +# Test objdump --debug=rawline on a file containing dwarf-5 embedded source
> +
> +if { ![is_elf_format] } then {
> +    unsupported "objdump --debug=rawline-embedded-source test"
> +} elseif { ![binutils_assemble $srcdir/$subdir/dwarf-embedded-source.S tmpdir/dwarf-embedded-source.${obj}] } then {
> +    fail "objdump --debug=rawline-embedded-source test"
> +} else {
> +    if [is_remote host] {
> +       set op_testfile [remote_download host tmpdir/dwarf-embedded-source.${obj}]
> +    } else {
> +       set op_testfile tmpdir/dwarf-embedded-source.${obj}
> +    }
> +
> +    set got [remote_exec host "$OBJDUMP $OBJDUMPFLAGS --dwarf=rawline $op_testfile" "" "/dev/null" "tmpdir/objdump.out"]
> +
> +    if { [regexp_diff tmpdir/objdump.out $srcdir/$subdir/dwarf-embedded-source.rawline] } then {
> +       fail "objdump --debug=rawline-embedded-source test"
> +    } else {
> +       pass "objdump --debug=rawline-embedded-source test"
> +    }
> +
> +    file_on_host delete $output
> +}

Is all of this necessary? Can't the test be done via a simple run_dump_test?

> --- a/include/dwarf2.h
> +++ b/include/dwarf2.h
> @@ -293,7 +293,9 @@ enum dwarf_line_number_content_type
>      DW_LNCT_timestamp = 0x3,
>      DW_LNCT_size = 0x4,
>      DW_LNCT_MD5 = 0x5,
> +    DW_LNCT_source = 0x6,

This isn't official yet aiui, so may want annotating accordingly.

More generally, Nick - what's the policy towards use of constants from not
yet released specifications (which hence may still change)?

Jan

>      DW_LNCT_lo_user = 0x2000,
> +    DW_LNCT_LLVM_source = 0x2001,
>      DW_LNCT_hi_user = 0x3fff
>    };
>
  
Will Hawkins April 22, 2026, 12:26 a.m. UTC | #3
Thank you for taking the time to give feedback!

On Tue, Apr 21, 2026 at 12:03 PM Jan Beulich <jbeulich@suse.com> wrote:
>
> On 21.04.2026 04:02, Will Hawkins wrote:
> > v3 should contain a test that passes on aarch64 as well as x86_64. Sorry
> > for the hassle!
>
> What about other targets, including 32-bit ones?

I, unfortunately, have not tested on that target, yet.

>
> > --- a/binutils/dwarf.c
> > +++ b/binutils/dwarf.c
> > @@ -5847,6 +5847,10 @@ display_formatted_table (unsigned char *data,
> >             case DW_LNCT_MD5:
> >               printf (_("\tMD5\t\t\t"));
> >               break;
> > +           case DW_LNCT_LLVM_source:
> > +           case DW_LNCT_source:
> > +             /* Skip source ... display on next line.  */
> > +             break;
>
> Just keep recorded that I'm concerned of such unqualified uses of constants
> from custom ranges (LLVM in this case).

Ack. I am absolutely not unconcerned ... just trying to help make
objdump even more of a great tool than it is!

>
> > @@ -5861,8 +5865,10 @@ display_formatted_table (unsigned char *data,
> >        unsigned char *datapass = data;
> >
> >        printf ("  %d", last_entry++);
> > -      /* Delay displaying name as the last entry for better screen layout.  */
> > -      for (namepass = 0; namepass < 2; namepass++)
> > +      /* Delay displaying name/source as the last entry for better screen
> > +      layout.  */
> > +      int namesourcepass;
> > +      for (namesourcepass = 0; namesourcepass < 3; namesourcepass++)
>
>       for (unsigned int namesourcepass = 0; namesourcepass < 3; namesourcepass++)
>
> (suitably line wrapped if necessary)

I surely would have written it the way that you suggested. However,
when I looked through existing code, I did not see any formatted this
way. I assumed that was because of an interest in maintaining
compatibility with C89. I will absolutely make the update, of course,
but I just wanted to make sure you knew that I was trying my best to
follow the project's standards.

>
> > @@ -5872,13 +5878,31 @@ display_formatted_table (unsigned char *data,
> >
> >             READ_ULEB (content_type, format, end);
> >             READ_ULEB (form, format, end);
> > -           bool do_loc = (content_type == DW_LNCT_path) != (namepass == 1);
> > +
> > +           bool do_loc = (content_type == DW_LNCT_path)
> > +             != (namesourcepass == 1);
> > +           do_loc |= (content_type == DW_LNCT_LLVM_source
> > +               || content_type == DW_LNCT_source)
> > +             != (namesourcepass == 2);
>
> Indentation is better now that it (iirc) was in v1, but it's still not
> quite right. Binary operators wrapped to the next line want to align
> with the corresponding part of the expression on the earlier line. At
> the example ...

I will gladly fix! I was also wondering: These expressions are hard to
grok. I found the meaning hard to identify even in the existing code.
With the additional elements, it now seems like it might be a good
idea to expand into an if statement? I'd love feedback!

>
> > +           char delimiter = '\t';
> > +
> > +           /* Print Source last (if available) and print it
> > +              starting on the next line.  */
> > +           if (namesourcepass == 2 && (content_type == DW_LNCT_LLVM_source
> > +                 || content_type == DW_LNCT_source))
>
> ... here:
>
>               if (namesourcepass == 2
>                   && (content_type == DW_LNCT_LLVM_source
>                       || content_type == DW_LNCT_source))
>
> > --- /dev/null
> > +++ b/binutils/testsuite/binutils-all/dwarf-embedded-source.S
> > @@ -0,0 +1,240 @@
> > +     .text
> > +     .file   "small.c"
> > +     .globl  main
> > +     .p2align        4, 0x90
> > +     .type   main,@function
> > +main:
> > +.Lbegin_func_main:
> > +.Lend_func_main:
> > +     .size   main, .Lend_func_main-main
> > +     .section        .debug_abbrev,"",@progbits
> > +     .byte   1                               /* Abbreviation Code */
> > +     .byte   17                              /* DW_TAG_compile_unit */
> > +     .byte   1                               /* DW_CHILDREN_yes */
> > +     .byte   37                              /* DW_AT_producer */
> > +     .byte   37                              /* DW_FORM_strx1 */
> > +     .byte   19                              /* DW_AT_language */
> > +     .byte   5                               /* DW_FORM_data2 */
> > +     .byte   3                               /* DW_AT_name */
> > +     .byte   37                              /* DW_FORM_strx1 */
> > +     .byte   114                             /* DW_AT_str_offsets_base */
> > +     .byte   23                              /* DW_FORM_sec_offset */
> > +     .byte   16                              /* DW_AT_stmt_list */
> > +     .byte   23                              /* DW_FORM_sec_offset */
> > +     .byte   27                              /* DW_AT_comp_dir */
> > +     .byte   37                              /* DW_FORM_strx1 */
> > +     .byte   17                              /* DW_AT_low_pc */
> > +     .byte   27                              /* DW_FORM_addrx */
> > +     .byte   18                              /* DW_AT_high_pc */
> > +     .byte   6                               /* DW_FORM_data4 */
> > +     .byte   115                             /* DW_AT_addr_base */
> > +     .byte   23                              /* DW_FORM_sec_offset */
> > +     .byte   0                               /* EOM(1) */
> > +     .byte   0                               /* EOM(2) */
> > +     .byte   2                               /* Abbreviation Code */
> > +     .byte   46                              /* DW_TAG_subprogram */
> > +     .byte   0                               /* DW_CHILDREN_no */
> > +     .byte   17                              /* DW_AT_low_pc */
> > +     .byte   27                              /* DW_FORM_addrx */
> > +     .byte   18                              /* DW_AT_high_pc */
> > +     .byte   6                               /* DW_FORM_data4 */
> > +     .byte   64                              /* DW_AT_frame_base */
> > +     .byte   24                              /* DW_FORM_exprloc */
> > +     .byte   3                               /* DW_AT_name */
> > +     .byte   37                              /* DW_FORM_strx1 */
> > +     .byte   58                              /* DW_AT_decl_file */
> > +     .byte   11                              /* DW_FORM_data1 */
> > +     .byte   59                              /* DW_AT_decl_line */
> > +     .byte   11                              /* DW_FORM_data1 */
> > +     .byte   73                              /* DW_AT_type */
> > +     .byte   19                              /* DW_FORM_ref4 */
> > +     .byte   63                              /* DW_AT_external */
> > +     .byte   25                              /* DW_FORM_flag_present */
> > +     .byte   0                               /* EOM(1) */
> > +     .byte   0                               /* EOM(2) */
> > +     .byte   3                               /* Abbreviation Code */
> > +     .byte   36                              /* DW_TAG_base_type */
> > +     .byte   0                               /* DW_CHILDREN_no */
> > +     .byte   3                               /* DW_AT_name */
> > +     .byte   37                              /* DW_FORM_strx1 */
> > +     .byte   62                              /* DW_AT_encoding */
> > +     .byte   11                              /* DW_FORM_data1 */
> > +     .byte   11                              /* DW_AT_byte_size */
> > +     .byte   11                              /* DW_FORM_data1 */
> > +     .byte   0                               /* EOM(1) */
> > +     .byte   0                               /* EOM(2) */
> > +     .byte   0                               /* EOM(3) */
> > +     .section        .debug_info,"",@progbits
> > +.Lcu_begin0:
> > +     .long   .Ldebug_info_end0-.Ldebug_info_start0 /* Length of Unit */
> > +.Ldebug_info_start0:
> > +     .short  5                               /* DWARF version number */
> > +     .byte   1                               /* DWARF Unit Type */
> > +     .byte   8                               /* Address Size (in bytes) */
> > +     .long   .debug_abbrev                   /* Offset Into Abbrev. Section */
> > +     .byte   1                               /* Abbrev [1] 0xc:0x2b DW_TAG_compile_unit */
> > +     .byte   0                               /* DW_AT_producer */
> > +     .short  29                              /* DW_AT_language */
> > +     .byte   1                               /* DW_AT_name */
> > +     .long   .Lstr_offsets_base0             /* DW_AT_str_offsets_base */
> > +     .long   .Lline_table_start0             /* DW_AT_stmt_list */
> > +     .byte   2                               /* DW_AT_comp_dir */
> > +     .byte   0                               /* DW_AT_low_pc */
> > +     .long   .Lend_func_main-.Lbegin_func_main   /* DW_AT_high_pc */
> > +     .long   .Laddr_table_base0              /* DW_AT_addr_base */
> > +     .byte   2                               /* Abbrev [2] 0x23:0xf DW_TAG_subprogram */
> > +     .byte   0                               /* DW_AT_low_pc */
> > +     .long   .Lend_func_main-.Lbegin_func_main   /* DW_AT_high_pc */
> > +     .byte   1                               /* DW_AT_frame_base */
> > +     .byte   86
> > +     .byte   3                               /* DW_AT_name */
> > +     .byte   0                               /* DW_AT_decl_file */
> > +     .byte   1                               /* DW_AT_decl_line */
> > +     .long   50                              /* DW_AT_type */
> > +                                                /* DW_AT_external */
> > +     .byte   3                               /* Abbrev [3] 0x32:0x4 DW_TAG_base_type */
> > +     .byte   4                               /* DW_AT_name */
> > +     .byte   5                               /* DW_AT_encoding */
> > +     .byte   4                               /* DW_AT_byte_size */
> > +     .byte   0                               /* End Of Children Mark */
> > +.Ldebug_info_end0:
> > +     .section        .debug_str_offsets,"",@progbits
> > +     .long   24                              /* Length of String Offsets Set */
> > +     .short  5
> > +     .short  0
> > +.Lstr_offsets_base0:
> > +     .section        .debug_str,"MS",@progbits,1
> > +.Linfo_string0:
> > +     .asciz  "clang (with hand edits)"       /* string offset=0 */
> > +.Linfo_string1:
> > +     .asciz  "small.c"                       /* string offset=44 */
> > +.Linfo_string2:
> > +     .asciz  "/path/to/code/"
> > +.Linfo_string3:
> > +     .asciz  "main"                          /* string offset=77 */
> > +.Linfo_string4:
> > +     .asciz  "int"                           /* string offset=82 */
> > +     .section        .debug_str_offsets,"",@progbits
> > +     .long   .Linfo_string0
> > +     .long   .Linfo_string1
> > +     .long   .Linfo_string2
> > +     .long   .Linfo_string3
> > +     .long   .Linfo_string4
> > +     .section        .debug_line_str,"MS",@progbits,1
> > +.Lline_string1:
> > +     .asciz  "/path/to/code/"                /* string offset=0 */
> > +.Lline_string2:
> > +     .asciz  "small.c"                       /* string offset=15 */
> > +.Lline_string3:
> > +     .asciz  "int main() {\n  return 0;\n}\n"/* string offset=23 */
> > +     .section        .debug_addr,"",@progbits
> > +     .long   .Ldebug_addr_end0-.Ldebug_addr_start0 /* Length of contribution */
> > +.Ldebug_addr_start0:
> > +     .short  5                               /* DWARF version number */
> > +     .byte   8                               /* Address size */
> > +     .byte   0                               /* Segment selector size */
> > +.Laddr_table_base0:
> > +     .quad   .Lbegin_func_main
> > +.Ldebug_addr_end0:
>
> Up to here the comments allow to reasonably follow what is there.
>
> > +     .section        .debug_line,"",@progbits
> > +.Lline_table_start0:
> > +     .byte 0x60
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x05
> > +     .byte 0x00
> > +     .byte 0x08
> > +     .byte 0x00
> > +     .byte 0x3E
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x01
> > +     .byte 0x01
> > +     .byte 0x01
> > +     .byte 0xfb
> > +     .byte 0x0e
> > +     .byte 0x0d
> > +     .byte 0x00
> > +     .byte 0x01
> > +     .byte 0x01
> > +     .byte 0x01
> > +     .byte 0x01
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x01
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x01
> > +     .byte 0x01
> > +     .byte 0x01
> > +     .byte 0x1f
> > +     .byte 0x01
> > +     .byte 0x00                            /* Offset to directory. */
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x04
> > +     .byte 0x01
> > +     .byte 0x1f
> > +     .byte 0x02
> > +     .byte 0x0f                            /* Offset to filename. */
> > +     .byte 0x05
> > +     .byte 0x1e
> > +     .byte 0x81
> > +     .byte 0x40
> > +     .byte 0x1f
> > +     .byte 0x01
> > +     .byte 0x0f
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x03
> > +     .byte 0xad
> > +     .byte 0x19
> > +     .byte 0x9b
> > +     .byte 0xfa
> > +     .byte 0x21
> > +     .byte 0x80
> > +     .byte 0x26
> > +     .byte 0xd0
> > +     .byte 0xf1
> > +     .byte 0xbe
> > +     .byte 0x37
> > +     .byte 0x41
> > +     .byte 0x65
> > +     .byte 0xa1
> > +     .byte 0x6d
> > +     .byte 0x17                            /* Offset to source code. */
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x04
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x09
> > +     .byte 0x02
> > +     .byte 0x10
> > +     .byte 0x11
> > +     .byte 0x40
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x00
> > +     .byte 0x01
> > +     .byte 0x05
> > +     .byte 0x05
> > +     .byte 0x0a
> > +     .byte 0xad
> > +     .byte 0x06
> > +     .byte 0x0b
> > +     .byte 0x2e
> > +     .byte 0x02
> > +     .byte 0x02
> > +     .byte 0x00
> > +     .byte 0x01
> > +     .byte 0x01
>
> The same cannot be said here, which makes this pretty hard to maintain (i.e.
> change if need be). Question is anyway - do we really need to resort to
> .byte to express what's wanted?

I will absolutely update -- your point is very well taken!

>
> > --- /dev/null
> > +++ b/binutils/testsuite/binutils-all/dwarf-embedded-source.rawline
> > @@ -0,0 +1,13 @@
> > +#...
> > + The Directory Table \(offset 0x22, lines 1, columns 1\):
> > +
> > +  Entry      Name
> > +  0  \(indirect line string, offset: 0\): \/path\/to\/code\/
> > +
> > + The File Name Table \(offset 0x31, lines 1, columns 4\):
> > +  Entry      Dir     MD5                             Name
> > +  0  0 0x6da1654137bef1d0268021fa9b19ad03    \(indirect line string, offset: 0xf\): small.c
> > +  Source: \(indirect line string, offset: 0x17\): int main\(\) {
> > +  return 0;
> > +}
>
> The figure braces would better also be escaped, I think.
>
> > --- a/binutils/testsuite/binutils-all/objdump.exp
> > +++ b/binutils/testsuite/binutils-all/objdump.exp
> > @@ -615,6 +615,30 @@ if { ![is_elf_format] } then {
> >      file_on_host delete $output
> >  }
> >
> > +# Test objdump --debug=rawline on a file containing dwarf-5 embedded source
> > +
> > +if { ![is_elf_format] } then {
> > +    unsupported "objdump --debug=rawline-embedded-source test"
> > +} elseif { ![binutils_assemble $srcdir/$subdir/dwarf-embedded-source.S tmpdir/dwarf-embedded-source.${obj}] } then {
> > +    fail "objdump --debug=rawline-embedded-source test"
> > +} else {
> > +    if [is_remote host] {
> > +       set op_testfile [remote_download host tmpdir/dwarf-embedded-source.${obj}]
> > +    } else {
> > +       set op_testfile tmpdir/dwarf-embedded-source.${obj}
> > +    }
> > +
> > +    set got [remote_exec host "$OBJDUMP $OBJDUMPFLAGS --dwarf=rawline $op_testfile" "" "/dev/null" "tmpdir/objdump.out"]
> > +
> > +    if { [regexp_diff tmpdir/objdump.out $srcdir/$subdir/dwarf-embedded-source.rawline] } then {
> > +       fail "objdump --debug=rawline-embedded-source test"
> > +    } else {
> > +       pass "objdump --debug=rawline-embedded-source test"
> > +    }
> > +
> > +    file_on_host delete $output
> > +}
>
> Is all of this necessary? Can't the test be done via a simple run_dump_test?

Good question! I will investigate!

>
> > --- a/include/dwarf2.h
> > +++ b/include/dwarf2.h
> > @@ -293,7 +293,9 @@ enum dwarf_line_number_content_type
> >      DW_LNCT_timestamp = 0x3,
> >      DW_LNCT_size = 0x4,
> >      DW_LNCT_MD5 = 0x5,
> > +    DW_LNCT_source = 0x6,
>
> This isn't official yet aiui, so may want annotating accordingly.
>
> More generally, Nick - what's the policy towards use of constants from not
> yet released specifications (which hence may still change)?
>
> Jan

Thank you, again, for your feedback! If the group decides that there
is no interest in supporting it, I'll be glad to back off!

Will

>
> >      DW_LNCT_lo_user = 0x2000,
> > +    DW_LNCT_LLVM_source = 0x2001,
> >      DW_LNCT_hi_user = 0x3fff
> >    };
> >
>
  
Jan Beulich April 22, 2026, 5:58 a.m. UTC | #4
On 22.04.2026 02:26, Will Hawkins wrote:
> On Tue, Apr 21, 2026 at 12:03 PM Jan Beulich <jbeulich@suse.com> wrote:
>> On 21.04.2026 04:02, Will Hawkins wrote:
>>> @@ -5861,8 +5865,10 @@ display_formatted_table (unsigned char *data,
>>>        unsigned char *datapass = data;
>>>
>>>        printf ("  %d", last_entry++);
>>> -      /* Delay displaying name as the last entry for better screen layout.  */
>>> -      for (namepass = 0; namepass < 2; namepass++)
>>> +      /* Delay displaying name/source as the last entry for better screen
>>> +      layout.  */
>>> +      int namesourcepass;
>>> +      for (namesourcepass = 0; namesourcepass < 3; namesourcepass++)
>>
>>       for (unsigned int namesourcepass = 0; namesourcepass < 3; namesourcepass++)
>>
>> (suitably line wrapped if necessary)
> 
> I surely would have written it the way that you suggested. However,
> when I looked through existing code, I did not see any formatted this
> way. I assumed that was because of an interest in maintaining
> compatibility with C89. I will absolutely make the update, of course,
> but I just wanted to make sure you knew that I was trying my best to
> follow the project's standards.

There may not be examples in this particular file, but after we switched
to C99, examples have appeared in various places of the codebase.

>>> @@ -5872,13 +5878,31 @@ display_formatted_table (unsigned char *data,
>>>
>>>             READ_ULEB (content_type, format, end);
>>>             READ_ULEB (form, format, end);
>>> -           bool do_loc = (content_type == DW_LNCT_path) != (namepass == 1);
>>> +
>>> +           bool do_loc = (content_type == DW_LNCT_path)
>>> +             != (namesourcepass == 1);
>>> +           do_loc |= (content_type == DW_LNCT_LLVM_source
>>> +               || content_type == DW_LNCT_source)
>>> +             != (namesourcepass == 2);
>>
>> Indentation is better now that it (iirc) was in v1, but it's still not
>> quite right. Binary operators wrapped to the next line want to align
>> with the corresponding part of the expression on the earlier line. At
>> the example ...
> 
> I will gladly fix! I was also wondering: These expressions are hard to
> grok. I found the meaning hard to identify even in the existing code.
> With the additional elements, it now seems like it might be a good
> idea to expand into an if statement? I'd love feedback!

Personally I prefer as-is (I'd even go as far as combining decl and
statement into just a decl), but of course this is entirely subjective.

Jan
  

Patch

diff --git a/binutils/dwarf.c b/binutils/dwarf.c
index fa8dce25..6e6a82e4 100644
--- a/binutils/dwarf.c
+++ b/binutils/dwarf.c
@@ -5847,6 +5847,10 @@  display_formatted_table (unsigned char *data,
 	      case DW_LNCT_MD5:
 		printf (_("\tMD5\t\t\t"));
 		break;
+	      case DW_LNCT_LLVM_source:
+	      case DW_LNCT_source:
+		/* Skip source ... display on next line.  */
+		break;
 	      default:
 		printf (_("\t(Unknown format content type %" PRIu64 ")"),
 			content_type);
@@ -5861,8 +5865,10 @@  display_formatted_table (unsigned char *data,
       unsigned char *datapass = data;
 
       printf ("  %d", last_entry++);
-      /* Delay displaying name as the last entry for better screen layout.  */
-      for (namepass = 0; namepass < 2; namepass++)
+      /* Delay displaying name/source as the last entry for better screen
+	 layout.  */
+      int namesourcepass;
+      for (namesourcepass = 0; namesourcepass < 3; namesourcepass++)
 	{
 	  format = format_start;
 	  data = datapass;
@@ -5872,13 +5878,31 @@  display_formatted_table (unsigned char *data,
 
 	      READ_ULEB (content_type, format, end);
 	      READ_ULEB (form, format, end);
-	      bool do_loc = (content_type == DW_LNCT_path) != (namepass == 1);
+
+	      bool do_loc = (content_type == DW_LNCT_path)
+		!= (namesourcepass == 1);
+	      do_loc |= (content_type == DW_LNCT_LLVM_source
+		  || content_type == DW_LNCT_source)
+		!= (namesourcepass == 2);
+
+	      char delimiter = '\t';
+
+	      /* Print Source last (if available) and print it
+		 starting on the next line.  */
+	      if (namesourcepass == 2 && (content_type == DW_LNCT_LLVM_source
+		    || content_type == DW_LNCT_source))
+		{
+		  delimiter = ' ';
+		  putchar ('\n');
+		  printf ("  Source:");
+		}
 	      data = read_and_display_attr_value (0, form, 0, start, data, end,
 						  0, linfo->li_address_size,
 						  linfo->li_offset_size,
 						  linfo->li_version, NULL,
-						  do_loc, section, NULL, '\t',
-						  -1, false, 0, 0, false);
+						  do_loc, section, NULL,
+						  delimiter, -1, false, 0, 0,
+						  false);
 	    }
 	}
 
diff --git a/binutils/testsuite/binutils-all/dwarf-embedded-source.S b/binutils/testsuite/binutils-all/dwarf-embedded-source.S
new file mode 100644
index 00000000..71273636
--- /dev/null
+++ b/binutils/testsuite/binutils-all/dwarf-embedded-source.S
@@ -0,0 +1,240 @@ 
+	.text
+	.file	"small.c"
+	.globl	main
+	.p2align	4, 0x90
+	.type	main,@function
+main:
+.Lbegin_func_main:
+.Lend_func_main:
+	.size	main, .Lend_func_main-main
+	.section	.debug_abbrev,"",@progbits
+	.byte	1                               /* Abbreviation Code */
+	.byte	17                              /* DW_TAG_compile_unit */
+	.byte	1                               /* DW_CHILDREN_yes */
+	.byte	37                              /* DW_AT_producer */
+	.byte	37                              /* DW_FORM_strx1 */
+	.byte	19                              /* DW_AT_language */
+	.byte	5                               /* DW_FORM_data2 */
+	.byte	3                               /* DW_AT_name */
+	.byte	37                              /* DW_FORM_strx1 */
+	.byte	114                             /* DW_AT_str_offsets_base */
+	.byte	23                              /* DW_FORM_sec_offset */
+	.byte	16                              /* DW_AT_stmt_list */
+	.byte	23                              /* DW_FORM_sec_offset */
+	.byte	27                              /* DW_AT_comp_dir */
+	.byte	37                              /* DW_FORM_strx1 */
+	.byte	17                              /* DW_AT_low_pc */
+	.byte	27                              /* DW_FORM_addrx */
+	.byte	18                              /* DW_AT_high_pc */
+	.byte	6                               /* DW_FORM_data4 */
+	.byte	115                             /* DW_AT_addr_base */
+	.byte	23                              /* DW_FORM_sec_offset */
+	.byte	0                               /* EOM(1) */
+	.byte	0                               /* EOM(2) */
+	.byte	2                               /* Abbreviation Code */
+	.byte	46                              /* DW_TAG_subprogram */
+	.byte	0                               /* DW_CHILDREN_no */
+	.byte	17                              /* DW_AT_low_pc */
+	.byte	27                              /* DW_FORM_addrx */
+	.byte	18                              /* DW_AT_high_pc */
+	.byte	6                               /* DW_FORM_data4 */
+	.byte	64                              /* DW_AT_frame_base */
+	.byte	24                              /* DW_FORM_exprloc */
+	.byte	3                               /* DW_AT_name */
+	.byte	37                              /* DW_FORM_strx1 */
+	.byte	58                              /* DW_AT_decl_file */
+	.byte	11                              /* DW_FORM_data1 */
+	.byte	59                              /* DW_AT_decl_line */
+	.byte	11                              /* DW_FORM_data1 */
+	.byte	73                              /* DW_AT_type */
+	.byte	19                              /* DW_FORM_ref4 */
+	.byte	63                              /* DW_AT_external */
+	.byte	25                              /* DW_FORM_flag_present */
+	.byte	0                               /* EOM(1) */
+	.byte	0                               /* EOM(2) */
+	.byte	3                               /* Abbreviation Code */
+	.byte	36                              /* DW_TAG_base_type */
+	.byte	0                               /* DW_CHILDREN_no */
+	.byte	3                               /* DW_AT_name */
+	.byte	37                              /* DW_FORM_strx1 */
+	.byte	62                              /* DW_AT_encoding */
+	.byte	11                              /* DW_FORM_data1 */
+	.byte	11                              /* DW_AT_byte_size */
+	.byte	11                              /* DW_FORM_data1 */
+	.byte	0                               /* EOM(1) */
+	.byte	0                               /* EOM(2) */
+	.byte	0                               /* EOM(3) */
+	.section	.debug_info,"",@progbits
+.Lcu_begin0:
+	.long	.Ldebug_info_end0-.Ldebug_info_start0 /* Length of Unit */
+.Ldebug_info_start0:
+	.short	5                               /* DWARF version number */
+	.byte	1                               /* DWARF Unit Type */
+	.byte	8                               /* Address Size (in bytes) */
+	.long	.debug_abbrev                   /* Offset Into Abbrev. Section */
+	.byte	1                               /* Abbrev [1] 0xc:0x2b DW_TAG_compile_unit */
+	.byte	0                               /* DW_AT_producer */
+	.short	29                              /* DW_AT_language */
+	.byte	1                               /* DW_AT_name */
+	.long	.Lstr_offsets_base0             /* DW_AT_str_offsets_base */
+	.long	.Lline_table_start0             /* DW_AT_stmt_list */
+	.byte	2                               /* DW_AT_comp_dir */
+	.byte	0                               /* DW_AT_low_pc */
+	.long	.Lend_func_main-.Lbegin_func_main   /* DW_AT_high_pc */
+	.long	.Laddr_table_base0              /* DW_AT_addr_base */
+	.byte	2                               /* Abbrev [2] 0x23:0xf DW_TAG_subprogram */
+	.byte	0                               /* DW_AT_low_pc */
+	.long	.Lend_func_main-.Lbegin_func_main   /* DW_AT_high_pc */
+	.byte	1                               /* DW_AT_frame_base */
+	.byte	86
+	.byte	3                               /* DW_AT_name */
+	.byte	0                               /* DW_AT_decl_file */
+	.byte	1                               /* DW_AT_decl_line */
+	.long	50                              /* DW_AT_type */
+                                                /* DW_AT_external */
+	.byte	3                               /* Abbrev [3] 0x32:0x4 DW_TAG_base_type */
+	.byte	4                               /* DW_AT_name */
+	.byte	5                               /* DW_AT_encoding */
+	.byte	4                               /* DW_AT_byte_size */
+	.byte	0                               /* End Of Children Mark */
+.Ldebug_info_end0:
+	.section	.debug_str_offsets,"",@progbits
+	.long	24                              /* Length of String Offsets Set */
+	.short	5
+	.short	0
+.Lstr_offsets_base0:
+	.section	.debug_str,"MS",@progbits,1
+.Linfo_string0:
+	.asciz	"clang (with hand edits)"       /* string offset=0 */
+.Linfo_string1:
+	.asciz	"small.c"                       /* string offset=44 */
+.Linfo_string2:
+	.asciz	"/path/to/code/"
+.Linfo_string3:
+	.asciz	"main"                          /* string offset=77 */
+.Linfo_string4:
+	.asciz	"int"                           /* string offset=82 */
+	.section	.debug_str_offsets,"",@progbits
+	.long	.Linfo_string0
+	.long	.Linfo_string1
+	.long	.Linfo_string2
+	.long	.Linfo_string3
+	.long	.Linfo_string4
+	.section	.debug_line_str,"MS",@progbits,1
+.Lline_string1:
+	.asciz	"/path/to/code/"                /* string offset=0 */
+.Lline_string2:
+	.asciz	"small.c"                       /* string offset=15 */
+.Lline_string3:
+	.asciz	"int main() {\n  return 0;\n}\n"/* string offset=23 */
+	.section	.debug_addr,"",@progbits
+	.long	.Ldebug_addr_end0-.Ldebug_addr_start0 /* Length of contribution */
+.Ldebug_addr_start0:
+	.short	5                               /* DWARF version number */
+	.byte	8                               /* Address size */
+	.byte	0                               /* Segment selector size */
+.Laddr_table_base0:
+	.quad	.Lbegin_func_main
+.Ldebug_addr_end0:
+	.section	.debug_line,"",@progbits
+.Lline_table_start0:
+	.byte 0x60
+	.byte 0x00
+	.byte 0x00
+	.byte 0x00
+	.byte 0x05
+	.byte 0x00
+	.byte 0x08
+	.byte 0x00
+	.byte 0x3E
+	.byte 0x00
+	.byte 0x00
+	.byte 0x00
+	.byte 0x01
+	.byte 0x01
+	.byte 0x01
+	.byte 0xfb
+	.byte 0x0e
+	.byte 0x0d
+	.byte 0x00
+	.byte 0x01
+	.byte 0x01
+	.byte 0x01
+	.byte 0x01
+	.byte 0x00
+	.byte 0x00
+	.byte 0x00
+	.byte 0x01
+	.byte 0x00
+	.byte 0x00
+	.byte 0x01
+	.byte 0x01
+	.byte 0x01
+	.byte 0x1f
+	.byte 0x01
+	.byte 0x00                            /* Offset to directory. */
+	.byte 0x00
+	.byte 0x00
+	.byte 0x00
+	.byte 0x04
+	.byte 0x01
+	.byte 0x1f
+	.byte 0x02
+	.byte 0x0f                            /* Offset to filename. */
+	.byte 0x05
+	.byte 0x1e
+	.byte 0x81
+	.byte 0x40
+	.byte 0x1f
+	.byte 0x01
+	.byte 0x0f
+	.byte 0x00
+	.byte 0x00
+	.byte 0x00
+	.byte 0x00
+	.byte 0x03
+	.byte 0xad
+	.byte 0x19
+	.byte 0x9b
+	.byte 0xfa
+	.byte 0x21
+	.byte 0x80
+	.byte 0x26
+	.byte 0xd0
+	.byte 0xf1
+	.byte 0xbe
+	.byte 0x37
+	.byte 0x41
+	.byte 0x65
+	.byte 0xa1
+	.byte 0x6d
+	.byte 0x17                            /* Offset to source code. */
+	.byte 0x00
+	.byte 0x00
+	.byte 0x00
+	.byte 0x04
+	.byte 0x00
+	.byte 0x00
+	.byte 0x09
+	.byte 0x02
+	.byte 0x10
+	.byte 0x11
+	.byte 0x40
+	.byte 0x00
+	.byte 0x00
+	.byte 0x00
+	.byte 0x00
+	.byte 0x00
+	.byte 0x01
+	.byte 0x05
+	.byte 0x05
+	.byte 0x0a
+	.byte 0xad
+	.byte 0x06
+	.byte 0x0b
+	.byte 0x2e
+	.byte 0x02
+	.byte 0x02
+	.byte 0x00
+	.byte 0x01
+	.byte 0x01
diff --git a/binutils/testsuite/binutils-all/dwarf-embedded-source.rawline b/binutils/testsuite/binutils-all/dwarf-embedded-source.rawline
new file mode 100644
index 00000000..eb49dc55
--- /dev/null
+++ b/binutils/testsuite/binutils-all/dwarf-embedded-source.rawline
@@ -0,0 +1,13 @@ 
+#...
+ The Directory Table \(offset 0x22, lines 1, columns 1\):
+
+  Entry	Name
+  0	\(indirect line string, offset: 0\): \/path\/to\/code\/
+
+ The File Name Table \(offset 0x31, lines 1, columns 4\):
+  Entry	Dir	MD5				Name
+  0	0 0x6da1654137bef1d0268021fa9b19ad03	\(indirect line string, offset: 0xf\): small.c
+  Source: \(indirect line string, offset: 0x17\): int main\(\) {
+  return 0;
+}
+#pass
diff --git a/binutils/testsuite/binutils-all/objdump.exp b/binutils/testsuite/binutils-all/objdump.exp
index 1bb8f55d..af0c63a8 100644
--- a/binutils/testsuite/binutils-all/objdump.exp
+++ b/binutils/testsuite/binutils-all/objdump.exp
@@ -615,6 +615,30 @@  if { ![is_elf_format] } then {
     file_on_host delete $output
 }
 
+# Test objdump --debug=rawline on a file containing dwarf-5 embedded source
+
+if { ![is_elf_format] } then {
+    unsupported "objdump --debug=rawline-embedded-source test"
+} elseif { ![binutils_assemble $srcdir/$subdir/dwarf-embedded-source.S tmpdir/dwarf-embedded-source.${obj}] } then {
+    fail "objdump --debug=rawline-embedded-source test"
+} else {
+    if [is_remote host] {
+       set op_testfile [remote_download host tmpdir/dwarf-embedded-source.${obj}]
+    } else {
+       set op_testfile tmpdir/dwarf-embedded-source.${obj}
+    }
+
+    set got [remote_exec host "$OBJDUMP $OBJDUMPFLAGS --dwarf=rawline $op_testfile" "" "/dev/null" "tmpdir/objdump.out"]
+
+    if { [regexp_diff tmpdir/objdump.out $srcdir/$subdir/dwarf-embedded-source.rawline] } then {
+       fail "objdump --debug=rawline-embedded-source test"
+    } else {
+       pass "objdump --debug=rawline-embedded-source test"
+    }
+
+    file_on_host delete $output
+}
+
 proc test_build_id_debuglink {option} {
     global srcdir
     global subdir
diff --git a/include/dwarf2.h b/include/dwarf2.h
index bf9287f1..ba3279f3 100644
--- a/include/dwarf2.h
+++ b/include/dwarf2.h
@@ -293,7 +293,9 @@  enum dwarf_line_number_content_type
     DW_LNCT_timestamp = 0x3,
     DW_LNCT_size = 0x4,
     DW_LNCT_MD5 = 0x5,
+    DW_LNCT_source = 0x6,
     DW_LNCT_lo_user = 0x2000,
+    DW_LNCT_LLVM_source = 0x2001,
     DW_LNCT_hi_user = 0x3fff
   };