# Copyright 2012-2018 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
load_lib dwarf.exp

# This test can only be run on targets which support DWARF-2 and use gas.
if {![dwarf2_support]} {
    return 0  
}

# Find length of addresses in bytes.
if {[is_64_target]} {
    set addr_len 8
} else {
    set addr_len 4
}

standard_testfile
set asmsrcfile [standard_output_file ${testfile}asm.S]
set asmobjfile [standard_output_file ${testfile}asm.o]
set srcabsdir [standard_output_file ${testfile}.d]
set srctmpfile tmp-${testfile}.c

# $srcdir may be relative.
if {[file pathtype $srcabsdir] != "absolute"} {
    untested "objdir pathtype is not absolute"
    return -1
}

set f [open $asmsrcfile "w"]
puts $f "/* DO NOT EDIT! GENERATED AUTOMATICALLY! */"

proc out_cu { name cu_dir cu_name line_dir line_name } {
    global f
    global addr_len

    puts -nonewline $f "\
.L${name}_begin:
	.4byte	.L${name}_end - .L${name}_start	/* Length of Compilation Unit */
.L${name}_start:
	.2byte	2				/* DWARF Version */
	.4byte	.Labbrev1_begin			/* Offset into abbrev section */
	.byte	${addr_len}			/* Pointer size */
"
    if { $cu_dir != "" } {
	puts $f "  .uleb128 ABBREV_COMP_DIR_NAME /* Abbrev: DW_TAG_compile_unit */"
    } else {
	puts $f "  .uleb128 ABBREV_NAME /* Abbrev: DW_TAG_compile_unit */"
    }
    puts -nonewline $f "\
	.ascii	\"GNU C\\0\"			/* DW_AT_producer */
	.byte	2				/* DW_AT_language (DW_LANG_C) */
	.4byte	.Lline_${name}_begin		/* DW_AT_stmt_list */
	.${addr_len}byte ${name}_start		/* DW_AT_low_pc */
	.${addr_len}byte ${name}_end		/* DW_AT_high_pc */
"
    if { $cu_dir != "" } {
	puts $f "  .ascii $cu_dir /* DW_AT_comp_dir */"
    }
    puts -nonewline $f "\
	.ascii	$cu_name			/* DW_AT_name */

	.uleb128	3			/* Abbrev: DW_TAG_subprogram */
	.asciz		\"${name}\"		/* DW_AT_name */
	.${addr_len}byte ${name}_start		/* DW_AT_low_pc */
	.${addr_len}byte ${name}_end		/* DW_AT_high_pc */

	.byte		0			/* End of children of CU */
.L${name}_end:
"
}

proc out_line { name cu_dir cu_name line_dir line_name } {
    global f
    global addr_len

    puts -nonewline $f "\
.Lline_${name}_begin:
	.4byte		.Lline_${name}_end - .Lline_${name}_start	/* Initial length */
.Lline_${name}_start:
	.2byte		2			/* Version */
	.4byte		.Lline_${name}_lines - .Lline_${name}_hdr	/* header_length */
.Lline_${name}_hdr:
	.byte		1			/* Minimum insn length */
	.byte		1			/* default_is_stmt */
	.byte		1			/* line_base */
	.byte		1			/* line_range */
	.byte		4			/* opcode_base */

	/* Standard lengths */
	.byte		0
	.byte		1
	.byte		1

	/* Include directories */
"
    if { $line_dir != "" } {
	puts $f "  .ascii $line_dir"
    }
    puts -nonewline $f "\
	.byte		0

	/* File names */
	.ascii	$line_name
"
    if { $line_dir != "" } {
	puts $f "  .uleb128 1"
    } else {
	puts $f "  .uleb128 0"
    }
    puts -nonewline $f "\
	.uleb128	0
	.uleb128	0

	.byte		0

.Lline_${name}_lines:
	.byte		3	/* DW_LNS_advance_line */
	.sleb128	998	/* ... to 999 */
	.byte		0	/* DW_LNE_set_address */
	.uleb128	${addr_len}+1
	.byte		2
	.${addr_len}byte ${name}_start
	.byte		1	/* DW_LNS_copy */
	.byte		3	/* DW_LNS_advance_line */
	.sleb128	1	/* ... to 1000 */
	.byte		0	/* DW_LNE_set_address */
	.uleb128	${addr_len}+1
	.byte		2
	.${addr_len}byte ${name}_end
	.byte		1	/* DW_LNS_copy */
	.byte		0	/* DW_LNE_end_of_sequence */
	.uleb128	1
	.byte		1
.Lline_${name}_end:
"
}

# IFSOME can be optionally _same or _different if >= 2 absolute directories are
# provided.  Then in the _different case the overriden directories have invalid
# XDIR value.

proc out_unit { func compdir ldir file ifsame } {
    set name "compdir_${compdir}_ldir_${ldir}_file_${file}${ifsame}"

    if { $compdir == "missing_" } {
	set cu_dir {}
    } elseif { $compdir == "relative" } {
	set cu_dir {COMPDIR "\0"}
    } elseif { $compdir == "absolute" } {
	set cu_dir {BDIR "/" COMPDIR "\0"}
    } else {
	error "compdir $compdir"
    }

    if { $ldir == "missing_" } {
	set line_dir {}
    } elseif { $ldir == "relative" } {
	set line_dir {LDIR "\0"}
    } elseif { $ldir == "absolute" } {
	set line_dir {BDIR "/" LDIR "\0"}
    } else {
	error "ldir $ldir"
    }

    if { $file == "basename" } {
	set cu_name {FILE "\0"}
    } elseif { $file == "relative" } {
	set cu_name {FDIR "/" FILE "\0"}
    } elseif { $file == "absolute" } {
	set cu_name {BDIR "/" FILE "\0"}
    } else {
	error "file $file"
    }
    set line_name $cu_name

    if { "$ifsame" == "_different" } {
	if { $file == "absolute" } {
	    if { $ldir == "absolute" } {
		set line_dir {XDIR "\0"}
	    }
	    if { $compdir == "absolute" } {
		set cu_dir {XDIR "\0"}
	    }
	} elseif { $ldir == "absolute" } {
	    if { $compdir == "absolute" } {
		set cu_dir {XDIR "\0"}
	    }
	} else {
	    error "not enough absolutes"
	}
    }

    $func $name $cu_dir $cu_name $line_dir $line_name
}

proc out_diff { func compdir ldir file } {
    set abscount 0
    if { $compdir == "absolute" } {
	incr abscount
    }
    if { $ldir == "absolute" } {
	incr abscount
    }
    if { $file == "absolute" } {
	incr abscount
    }
    if { $abscount <= 1 } {
	out_unit $func $compdir $ldir $file ""
    } else {
	out_unit $func $compdir $ldir $file "_same"
	out_unit $func $compdir $ldir $file "_different"
    }
}

proc out_file { func compdir ldir } {
    out_diff $func $compdir $ldir "basename"
    out_diff $func $compdir $ldir "relative"
    out_diff $func $compdir $ldir "absolute"
}

proc out_ldir { func compdir } {
    out_file $func $compdir "missing_"
    out_file $func $compdir "relative"
    out_file $func $compdir "absolute"
}

proc out_compdir { func } {
    out_ldir $func "missing_"
    out_ldir $func "relative"
    out_ldir $func "absolute"
}

puts -nonewline $f "\
#define ABBREV_NAME 1
#define ABBREV_COMP_DIR_NAME 2
  .section .debug_info
"
out_compdir out_cu

puts $f "  .section .debug_line"
out_compdir out_line

puts -nonewline $f "\
	.section .debug_abbrev
.Labbrev1_begin:

	.uleb128	ABBREV_NAME		/* Abbrev code */
	.uleb128	0x11			/* DW_TAG_compile_unit */
	.byte		1			/* has_children */
	.uleb128	0x25			/* DW_AT_producer */
	.uleb128	0x8			/* DW_FORM_string */
	.uleb128	0x13			/* DW_AT_language */
	.uleb128	0xb			/* DW_FORM_data1 */
	.uleb128	0x10			/* DW_AT_stmt_list */
	.uleb128	0x6			/* DW_FORM_data4 */
	.uleb128	0x11			/* DW_AT_low_pc */
	.uleb128	0x1			/* DW_FORM_addr */
	.uleb128	0x12			/* DW_AT_high_pc */
	.uleb128	0x1			/* DW_FORM_addr */
	.uleb128	0x3			/* DW_AT_name */
	.uleb128	0x8			/* DW_FORM_string */
	.byte		0x0			/* Terminator */
	.byte		0x0			/* Terminator */

	.uleb128	ABBREV_COMP_DIR_NAME	/* Abbrev code */
	.uleb128	0x11			/* DW_TAG_compile_unit */
	.byte		1			/* has_children */
	.uleb128	0x25			/* DW_AT_producer */
	.uleb128	0x8			/* DW_FORM_string */
	.uleb128	0x13			/* DW_AT_language */
	.uleb128	0xb			/* DW_FORM_data1 */
	.uleb128	0x10			/* DW_AT_stmt_list */
	.uleb128	0x6			/* DW_FORM_data4 */
	.uleb128	0x11			/* DW_AT_low_pc */
	.uleb128	0x1			/* DW_FORM_addr */
	.uleb128	0x12			/* DW_AT_high_pc */
	.uleb128	0x1			/* DW_FORM_addr */
	.uleb128	0x1b			/* DW_AT_comp_dir */
	.uleb128	0x8			/* DW_FORM_string */
	.uleb128	0x3			/* DW_AT_name */
	.uleb128	0x8			/* DW_FORM_string */
	.byte		0x0			/* Terminator */
	.byte		0x0			/* Terminator */

	.uleb128	3			/* Abbrev code */
	.uleb128	0x2e			/* DW_TAG_subprogram */
	.byte		0			/* has_children */
	.uleb128	0x3			/* DW_AT_name */
	.uleb128	0x8			/* DW_FORM_string */
	.uleb128	0x11			/* DW_AT_low_pc */
	.uleb128	0x1			/* DW_FORM_addr */
	.uleb128	0x12			/* DW_AT_high_pc */
	.uleb128	0x1			/* DW_FORM_addr */
	.byte		0x0			/* Terminator */
	.byte		0x0			/* Terminator */

	.byte		0x0			/* Terminator */
	.byte		0x0			/* Terminator */
"

close $f

set opts {}
# Base directory.
lappend opts "additional_flags=-DBDIR=\"${srcabsdir}\""
# Incorrect directory which should never be visible from GDB.
lappend opts "additional_flags=-DXDIR=\"${srcabsdir}/xdir\""
# CU's DW_AT_comp_dir.
lappend opts "additional_flags=-DCOMPDIR=\"compdir\""
# .debug_line's directory.
lappend opts "additional_flags=-DLDIR=\"ldir\""
# CU's DW_AT_name and .debug_line's filename relative directory, if needed.
lappend opts "additional_flags=-DFDIR=\"fdir\""
# CU's DW_AT_name and .debug_line's filename.
lappend opts "additional_flags=-DFILE=\"${srctmpfile}\""

if { [gdb_compile "${asmsrcfile} ${srcdir}/${subdir}/$srcfile" "${binfile}" executable $opts] != "" } {
    untested "failed to compile"
    return -1
}

set dirs {}
foreach r {"" /rdir} {
  foreach x {"" /xdir} {
      foreach comp {"" /compdir} {
	  foreach l {"" /ldir} {
	      foreach f {"" /fdir} {
		  lappend dirs $r$x$comp$l$f
	      }
	  }
      }
  }
}

proc pathexpand {prefix dirlst suffix} {
    set retlst {}
    foreach dir $dirlst {
	lappend retlst "$prefix$dir$suffix"
    }
    return $retlst
}

set filelist [pathexpand $srcabsdir $dirs "/$srctmpfile"]
set dircreatelist [pathexpand $srcabsdir $dirs ""]
set dirremovelist [pathexpand $srcabsdir [lreverse $dirs] ""]

remote_exec host "sh -c \"rm -f $filelist\""
remote_exec host "sh -c \"rmdir $dirremovelist\""
remote_exec host "sh -c \"mkdir $dircreatelist\""
remote_exec host "sh -c \"for d in $dircreatelist; do cp ${srcdir}/${subdir}/${srcfile} \\\$d/${srctmpfile}; done\""

clean_restart ${testfile}

if ![runto_main] {
    return -1
}

gdb_test "cd ${srcabsdir}/rdir" "Working directory [string_to_regexp ${srcabsdir}]/rdir\\." "cd .../rdir"

proc test { func compdir filename } {
    with_test_prefix "$func" {
	# Clear the GDB cache.
	gdb_test_no_output "set directories" ""

	if {$compdir == ""} {
	    set absolute "$filename"
	} else {
	    set absolute "$compdir/$filename"
	}
	if {[string index $absolute 0] != "/"} {
	    error "not absolute"
	}

	gdb_breakpoint $func
	gdb_continue_to_breakpoint $func "$func \\(\\) at .*"

	gdb_test_no_output "set filename-display absolute"
	verbose -log "expect: ${absolute}"
	gdb_test "frame" " in $func \\(\\) at [string_to_regexp ${absolute}]:999" "absolute"

	gdb_test_no_output "set filename-display basename"
	verbose -log "expect: [file tail $filename]"
	gdb_test "frame" " in $func \\(\\) at [string_to_regexp [file tail $filename]]:999" "basename"

	gdb_test_no_output "set filename-display relative"
	verbose -log "expect: $filename"
	gdb_test "frame" " in $func \\(\\) at [string_to_regexp $filename]:999" "relative"
    }
}

set bdir "${srcabsdir}"
set file "${srctmpfile}"
test "compdir_missing__ldir_missing__file_basename" "$bdir/rdir" "$file"
test "compdir_missing__ldir_missing__file_relative" "$bdir/rdir" "fdir/$file"
test "compdir_missing__ldir_missing__file_absolute" "" "$bdir/$file"
test "compdir_missing__ldir_relative_file_basename" "$bdir/rdir" "ldir/$file"
test "compdir_missing__ldir_relative_file_relative" "$bdir/rdir" "ldir/fdir/$file"
test "compdir_missing__ldir_relative_file_absolute" "" "$bdir/$file"
test "compdir_missing__ldir_absolute_file_basename" "" "$bdir/ldir/$file"
test "compdir_missing__ldir_absolute_file_relative" "" "$bdir/ldir/fdir/$file"
test "compdir_missing__ldir_absolute_file_absolute_same" "" "$bdir/$file"
test "compdir_missing__ldir_absolute_file_absolute_different" "" "$bdir/$file"
test "compdir_relative_ldir_missing__file_basename" "$bdir/rdir/compdir" "$file"
test "compdir_relative_ldir_missing__file_relative" "$bdir/rdir/compdir" "fdir/$file"
test "compdir_relative_ldir_missing__file_absolute" "" "$bdir/$file"
test "compdir_relative_ldir_relative_file_basename" "$bdir/rdir/compdir" "ldir/$file"
test "compdir_relative_ldir_relative_file_relative" "$bdir/rdir/compdir" "ldir/fdir/$file"
test "compdir_relative_ldir_relative_file_absolute" "" "$bdir/$file"
test "compdir_relative_ldir_absolute_file_basename" "" "$bdir/ldir/$file"
test "compdir_relative_ldir_absolute_file_relative" "" "$bdir/ldir/fdir/$file"
test "compdir_relative_ldir_absolute_file_absolute_same" "" "$bdir/$file"
test "compdir_relative_ldir_absolute_file_absolute_different" "" "$bdir/$file"
test "compdir_absolute_ldir_missing__file_basename" "$bdir/compdir" "$file"
test "compdir_absolute_ldir_missing__file_relative" "$bdir/compdir" "fdir/$file"
test "compdir_absolute_ldir_missing__file_absolute_same" "" "$bdir/$file"
test "compdir_absolute_ldir_missing__file_absolute_different" "" "$bdir/$file"
test "compdir_absolute_ldir_relative_file_basename" "$bdir/compdir" "ldir/$file"
test "compdir_absolute_ldir_relative_file_relative" "$bdir/compdir" "ldir/fdir/$file"
test "compdir_absolute_ldir_relative_file_absolute_same" "" "$bdir/$file"
test "compdir_absolute_ldir_relative_file_absolute_different" "" "$bdir/$file"
test "compdir_absolute_ldir_absolute_file_basename_same" "" "$bdir/ldir/$file"
test "compdir_absolute_ldir_absolute_file_relative_different" "" "$bdir/ldir/fdir/$file"
test "compdir_absolute_ldir_absolute_file_absolute_same" "" "$bdir/$file"
test "compdir_absolute_ldir_absolute_file_absolute_different" "" "$bdir/$file"