(root)/
binutils-2.41/
ld/
testsuite/
ld-pe/
pdb.exp
# Expect script for creating PDB files when linking.
#   Copyright (C) 2022-2023 Free Software Foundation, Inc.
#
# This file is part of the GNU Binutils.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
# MA 02110-1301, USA.

if {![istarget i*86-*-mingw*]
  && ![istarget i*86-*-cygwin*]
  && ![istarget i*86-*-winnt]
  && ![istarget i*86-*-pe]
  && ![istarget x86_64-*-mingw*]
  && ![istarget x86_64-*-pe*]
  && ![istarget x86_64-*-cygwin]
  && ![istarget aarch64-*-mingw*]
  && ![istarget aarch64-*-pe*]} {
    return
}

proc get_pdb_name { pe } {
    global OBJDUMP

    set exec_output [run_host_cmd "$OBJDUMP" "-p $pe"]

    if ![regexp -line "^\\(format RSDS signature (\[0-9a-fA-F\]{32}) age 1 pdb (.*)\\)$" $exec_output full sig pdb] {
	return ""
    }

    return $pdb
}

proc get_pdb_guid { pe } {
    global OBJDUMP

    set exec_output [run_host_cmd "$OBJDUMP" "-p $pe"]

    if ![regexp -line "^\\(format RSDS signature (\[0-9a-fA-F\]{32}) age 1 pdb (.*)\\)$" $exec_output full sig pdb] {
	return ""
    }

    return $sig
}

proc check_pdb_info_stream { pdb guid } {
    global ar

    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb 0001"]

    if ![string match "" $exec_output] {
	return 0
    }

    set fi [open tmpdir/0001]
    fconfigure $fi -translation binary

    # check version

    set data [read $fi 4]
    binary scan $data i version

    if { $version != 20000404 } {
	close $fi
	return 0
    }

    # skip signature (timestamp)
    read $fi 4

    # check age

    set data [read $fi 4]
    binary scan $data i age

    if { $age != 1 } {
	close $fi
	return 0
    }

    # check GUID

    set data [read $fi 16]
    binary scan $data H2H2H2H2H2H2H2H2H* guid1 guid2 guid3 guid4 guid5 guid6 guid7 guid8 guid9

    set data "$guid4$guid3$guid2$guid1$guid6$guid5$guid8$guid7$guid9"

    if { $data ne $guid } {
	close $fi
	return 0
    }

    # skip names string

    set data [read $fi 4]
    binary scan $data i names_length
    read $fi $names_length

    # read number of names entries

    set data [read $fi 4]
    binary scan $data i num_entries

    # skip number of buckets
    read $fi 4

    # skip present bitmap

    set data [read $fi 4]
    binary scan $data i bitmap_length
    read $fi [expr $bitmap_length * 4]

    # skip deleted bitmap

    set data [read $fi 4]
    binary scan $data i bitmap_length
    read $fi [expr $bitmap_length * 4]

    # skip names entries
    read $fi [expr $num_entries * 8]

    # skip uint32_t
    read $fi 4

    # read second version

    set data [read $fi 4]
    binary scan $data i version2

    if { $version2 != 20140508 } {
	close $fi
	return 0
    }

    close $fi

    return 1
}

proc check_type_stream { pdb stream } {
    global ar

    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb $stream"]

    if ![string match "" $exec_output] {
	return 0
    }

    set fi [open tmpdir/$stream]
    fconfigure $fi -translation binary

    # check version

    set data [read $fi 4]
    binary scan $data i version

    if { $version != 20040203 } {
	close $fi
	return 0
    }

    # check header size

    set data [read $fi 4]
    binary scan $data i header_size

    if { $header_size != 0x38 } {
	close $fi
	return 0
    }

    # skip type_index_begin and type_index_end
    read $fi 8

    # read type_record_bytes

    set data [read $fi 4]
    binary scan $data i type_record_bytes

    close $fi

    # check stream length

    set stream_length [file size tmpdir/$stream]

    if { $stream_length != [ expr $header_size + $type_record_bytes ] } {
	return 0
    }

    return 1
}

proc check_dbi_stream { pdb } {
    global ar

    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb 0003"]

    if ![string match "" $exec_output] {
	return 0
    }

    set fi [open tmpdir/0003]
    fconfigure $fi -translation binary

    # check signature

    set data [read $fi 4]
    binary scan $data i signature

    if { $signature != -1 } {
	close $fi
	return 0
    }

    # check version

    set data [read $fi 4]
    binary scan $data i version

    if { $version != 19990903 } {
	close $fi
	return 0
    }

    # check age

    set data [read $fi 4]
    binary scan $data i age

    if { $age != 1 } {
	close $fi
	return 0
    }

    # skip fields
    read $fi 12

    # read substream sizes

    set data [read $fi 4]
    binary scan $data i mod_info_size

    set data [read $fi 4]
    binary scan $data i section_contribution_size

    set data [read $fi 4]
    binary scan $data i section_map_size

    set data [read $fi 4]
    binary scan $data i source_info_size

    set data [read $fi 4]
    binary scan $data i type_server_map_size

    # skip MFC type server index
    seek $fi 4 current

    set data [read $fi 4]
    binary scan $data i optional_dbg_header_size

    set data [read $fi 4]
    binary scan $data i ec_substream_size

    close $fi

    # check stream length

    set stream_length [file size tmpdir/0003]

    if { $stream_length != [expr 0x40 + $mod_info_size + $section_contribution_size + $section_map_size + $source_info_size + $type_server_map_size + $optional_dbg_header_size + $ec_substream_size] } {
	return 0
    }

    return 1
}

proc get_section_stream_index { pdb } {
    global ar

    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb 0003"]

    if ![string match "" $exec_output] {
	return -1
    }

    set fi [open tmpdir/0003]
    fconfigure $fi -translation binary

    # skip fields
    seek $fi 24

    # read substream sizes

    set data [read $fi 4]
    binary scan $data i mod_info_size

    set data [read $fi 4]
    binary scan $data i section_contribution_size

    set data [read $fi 4]
    binary scan $data i section_map_size

    set data [read $fi 4]
    binary scan $data i source_info_size

    set data [read $fi 4]
    binary scan $data i type_server_map_size

    # skip type server index
    seek $fi 4 current

    set data [read $fi 4]
    binary scan $data i optional_dbg_header_size

    if { $optional_dbg_header_size < 12 } {
	close $fi
	return -1
    }

    # skip data
    seek $fi [expr 12 + $mod_info_size + $section_contribution_size + $section_map_size + $source_info_size + $type_server_map_size + 10] current

    set data [read $fi 2]
    binary scan $data s section_stream_index

    close $fi

    return $section_stream_index
}

proc check_section_stream { img pdb } {
    global ar

    # read sections stream

    set index [get_section_stream_index $pdb]

    if { $index == -1 } {
	return 0
    }

    set index_str [format "%04x" $index]

    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb $index_str"]

    if ![string match "" $exec_output] {
	return 0
    }

    set stream_length [file size tmpdir/$index_str]

    set fi [open tmpdir/$index_str]
    fconfigure $fi -translation binary

    set stream_data [read $fi $stream_length]

    close $fi

    # read sections from PE file

    set fi [open $img]
    fconfigure $fi -translation binary

    # read PE offset
    read $fi 0x3c
    set data [read $fi 4]
    binary scan $data i pe_offset

    # read number of sections
    seek $fi [expr $pe_offset + 6]
    set data [read $fi 2]
    binary scan $data s num_sections

    # read size of optional header
    seek $fi 12 current
    set data [read $fi 2]
    binary scan $data s opt_header_size

    # read section headers
    seek $fi [expr $opt_header_size + 2] current
    set section_data [read $fi [expr $num_sections * 40]]

    close $fi

    # compare

    if { $stream_data ne $section_data} {
	return 0
    }

    return 1
}

proc get_publics_stream_index { pdb } {
    global ar

    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb 0003"]

    if ![string match "" $exec_output] {
	return -1
    }

    set fi [open tmpdir/0003]
    fconfigure $fi -translation binary

    # skip fields
    seek $fi 16

    # read substream sizes

    set data [read $fi 2]
    binary scan $data s index

    close $fi

    return $index
}

proc get_sym_record_stream_index { pdb } {
    global ar

    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb 0003"]

    if ![string match "" $exec_output] {
	return -1
    }

    set fi [open tmpdir/0003]
    fconfigure $fi -translation binary

    # skip fields
    seek $fi 20

    # read substream sizes

    set data [read $fi 2]
    binary scan $data s index

    close $fi

    return $index
}

proc check_publics_stream { pdb } {
    global ar
    global objdump
    global srcdir
    global subdir

    set publics_index [get_publics_stream_index $pdb]

    if { $publics_index == -1 } {
	return 0
    }

    set index_str [format "%04x" $publics_index]

    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb $index_str"]

    if ![string match "" $exec_output] {
	return 0
    }

    set exp [file_contents "$srcdir/$subdir/pdb1-publics.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]
    if ![string match $exp $got] {
	return 0
    }

    set sym_record_index [get_sym_record_stream_index $pdb]

    if { $sym_record_index == -1 } {
	return 0
    }

    set index_str [format "%04x" $sym_record_index]

    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb $index_str"]

    if ![string match "" $exec_output] {
	return 0
    }

    set exp [file_contents "$srcdir/$subdir/pdb1-sym-record.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]
    if ![string match $exp $got] {
	return 0
    }

    return 1
}

proc test1 { } {
    global as
    global ld
    global srcdir
    global subdir

    if ![ld_assemble $as $srcdir/$subdir/pdb1.s tmpdir/pdb1.o] {
	unsupported "Build pdb1.o"
	return
    }

    if ![ld_link $ld "tmpdir/pdb1.exe" "--pdb=tmpdir/pdb1.pdb --gc-sections -e foo tmpdir/pdb1.o"] {
	fail "Could not create a PE image with a PDB file"
	return
    }

    if ![string equal [get_pdb_name "tmpdir/pdb1.exe"] "pdb1.pdb"] {
	fail "PDB filename not found in CodeView debug info"
	return
    }

    pass "PDB filename present in CodeView debug info"

    if [check_pdb_info_stream tmpdir/pdb1.pdb [get_pdb_guid "tmpdir/pdb1.exe"]] {
	pass "Valid PDB info stream"
    } else {
	fail "Invalid PDB info stream"
    }

    if [check_type_stream tmpdir/pdb1.pdb "0002"] {
	pass "Valid TPI stream"
    } else {
	fail "Invalid TPI stream"
    }

    if [check_type_stream tmpdir/pdb1.pdb "0004"] {
	pass "Valid IPI stream"
    } else {
	fail "Invalid IPI stream"
    }

    if [check_dbi_stream tmpdir/pdb1.pdb] {
	pass "Valid DBI stream"
    } else {
	fail "Invalid DBI stream"
    }

    if [check_section_stream tmpdir/pdb1.exe tmpdir/pdb1.pdb] {
	pass "Valid section stream"
    } else {
	fail "Invalid section stream"
    }

    if [check_publics_stream tmpdir/pdb1.pdb] {
	pass "Valid publics stream"
    } else {
	fail "Invalid publics stream"
    }
}

proc test_mod_info { mod_info } {
    # check filenames in mod_info

    set off 64

    set obj1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
    incr off [expr [string length $obj1] + 1]

    set ar1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
    incr off [expr [string length $ar1] + 1]

    if [string match "*pdb2a.o" $obj1] {
	pass "Correct name for first object file"
    } else {
	fail "Incorrect name for first object file"
    }

    if [string equal $obj1 $ar1] {
	pass "Correct archive name for first object file"
    } else {
	fail "Incorrect archive name for first object file"
    }

    if { [expr $off % 4] != 0 } {
	set off [expr $off + 4 - ($off % 4)]
    }

    incr off 64

    set obj2 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
    incr off [expr [string length $obj2] + 1]

    set ar2 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
    incr off [expr [string length $ar2] + 1]

    if [string match "*pdb2b.o" $obj2] {
	pass "Correct name for second object file"
    } else {
	fail "Incorrect name for second object file"
    }

    if [string match "*pdb2b.a" $ar2] {
	pass "Correct archive name for second object file"
    } else {
	fail "Incorrect archive name for second object file"
    }

    if { [expr $off % 4] != 0 } {
	set off [expr $off + 4 - ($off % 4)]
    }

    incr off 64

    set obj3 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
    incr off [expr [string length $obj3] + 1]

    set ar3 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
    incr off [expr [string length $ar3] + 1]

    if [string equal $obj3 "* Linker *"] {
	pass "Correct name for dummy object file"
    } else {
	fail "Incorrect name for dummy object file"
    }

    if [string equal $ar3 ""] {
	pass "Correct archive name for dummy object file"
    } else {
	fail "Incorrect archive name for dummy object file"
    }
}

proc test_section_contrib { section_contrib } {
    global objdump
    global srcdir
    global subdir

    set fi [open tmpdir/pdb2-sc w]
    fconfigure $fi -translation binary
    puts -nonewline $fi $section_contrib
    close $fi

    set exp [file_contents "$srcdir/$subdir/pdb2-section-contrib.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb2-sc"]

    if [string equal $exp $got] {
	pass "Correct section contribution substream"
    } else {
	fail "Incorrect section contribution substream"
    }
}

proc test2 { } {
    global as
    global ar
    global ld
    global srcdir
    global subdir

    if ![ld_assemble $as $srcdir/$subdir/pdb2a.s tmpdir/pdb2a.o] {
	unsupported "Build pdb2a.o"
	return
    }

    if ![ld_assemble $as $srcdir/$subdir/pdb2b.s tmpdir/pdb2b.o] {
	unsupported "Build pdb2b.o"
	return
    }

    set exec_output [run_host_cmd "$ar" "cr tmpdir/pdb2b.a tmpdir/pdb2b.o"]

    if ![string match "" $exec_output] {
	unsupported "Create pdb2b.a"
	return
    }

    if ![ld_link $ld "tmpdir/pdb2.exe" "--pdb=tmpdir/pdb2.pdb --gc-sections -e foo tmpdir/pdb2a.o tmpdir/pdb2b.a"] {
	unsupported "Create PE image with PDB file"
	return
    }

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb2.pdb 0003"]

    if ![string match "" $exec_output] {
	return 0
    }

    set fi [open tmpdir/0003]
    fconfigure $fi -translation binary

    seek $fi 24

    set data [read $fi 4]
    binary scan $data i mod_info_size

    set data [read $fi 4]
    binary scan $data i section_contrib_size

    seek $fi 32 current

    set mod_info [read $fi $mod_info_size]
    set section_contrib [read $fi $section_contrib_size]

    close $fi

    test_mod_info $mod_info
    test_section_contrib $section_contrib
}

proc find_named_stream { pdb name } {
    global ar

    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb 0001"]

    if ![string match "" $exec_output] {
	return 0
    }

    set fi [open tmpdir/0001]
    fconfigure $fi -translation binary

    seek $fi 0x1c

    set data [read $fi 4]
    binary scan $data i string_len

    set strings [read $fi $string_len]

    set string_off 0

    while {[string first \000 $strings $string_off] != -1 } {
	set str [string range $strings $string_off [expr [string first \000 $strings $string_off] - 1]]

	if { $str eq $name } {
	    break
	}

	incr string_off [expr [string length $str] + 1]
    }

    if { [string length $strings] == $string_off } { # string not found
	close $fi
	return 0
    }

    set data [read $fi 4]
    binary scan $data i num_entries

    seek $fi 4 current

    set data [read $fi 4]
    binary scan $data i present_bitmap_len

    seek $fi [expr $present_bitmap_len * 4] current

    set data [read $fi 4]
    binary scan $data i deleted_bitmap_len

    seek $fi [expr $deleted_bitmap_len * 4] current

    for {set i 0} {$i < $num_entries} {incr i} {
	set data [read $fi 4]
	binary scan $data i offset

	if { $offset == $string_off } {
	    set data [read $fi 4]
	    binary scan $data i value
	    close $fi

	    return $value
	}

	seek $fi 4 current
    }

    close $fi

    return 0
}

proc test3 { } {
    global as
    global ar
    global ld
    global objdump
    global srcdir
    global subdir

    if ![ld_assemble $as $srcdir/$subdir/pdb-strings1.s tmpdir/pdb-strings1.o] {
	unsupported "Build pdb-strings1.o"
	return
    }

    if ![ld_assemble $as $srcdir/$subdir/pdb-strings2.s tmpdir/pdb-strings2.o] {
	unsupported "Build pdb-strings2.o"
	return
    }

    if ![ld_link $ld "tmpdir/pdb-strings.exe" "--pdb=tmpdir/pdb-strings.pdb tmpdir/pdb-strings1.o tmpdir/pdb-strings2.o"] {
	unsupported "Create PE image with PDB file"
	return
    }

    set index [find_named_stream "tmpdir/pdb-strings.pdb" "/names"]

    if { $index == 0 } {
	fail "Could not find /names stream"
	return
    } else {
	pass "Found /names stream"
    }

    set index_str [format "%04x" $index]

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-strings.pdb $index_str"]

    if ![string match "" $exec_output] {
	return 0
    }

    set exp [file_contents "$srcdir/$subdir/pdb-strings.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]

    if ![string match $exp $got] {
	fail "Strings table was not as expected"
    } else {
	pass "Strings table was as expected"
    }
}

proc extract_c13_info { pdb mod_info } {
    global ar

    binary scan [string range $mod_info 34 35] s module_sym_stream
    binary scan [string range $mod_info 36 39] i sym_byte_size
    binary scan [string range $mod_info 40 43] i c11_byte_size
    binary scan [string range $mod_info 44 47] i c13_byte_size

    set index_str [format "%04x" $module_sym_stream]

    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb $index_str"]

    if ![string match "" $exec_output] {
	return ""
    }

    set fi [open tmpdir/$index_str]
    fconfigure $fi -translation binary

    seek $fi [expr $sym_byte_size + $c11_byte_size]

    set data [read $fi $c13_byte_size]

    close $fi

    return $data
}

proc test4 { } {
    global as
    global ar
    global ld
    global objdump
    global srcdir
    global subdir

    if ![ld_assemble $as $srcdir/$subdir/pdb3a.s tmpdir/pdb3a.o] {
	unsupported "Build pdb3a.o"
	return
    }

    if ![ld_assemble $as $srcdir/$subdir/pdb3b.s tmpdir/pdb3b.o] {
	unsupported "Build pdb3b.o"
	return
    }

    if ![ld_link $ld "tmpdir/pdb3.exe" "--pdb=tmpdir/pdb3.pdb --gc-sections -e main tmpdir/pdb3a.o tmpdir/pdb3b.o"] {
	unsupported "Create PE image with PDB file"
	return
    }

    # read relevant bits from DBI stream

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb3.pdb 0003"]

    if ![string match "" $exec_output] {
	fail "Could not extract DBI stream"
	return
    } else {
	pass "Extracted DBI stream"
    }

    set fi [open tmpdir/0003]
    fconfigure $fi -translation binary

    seek $fi 24

    # read substream sizes

    set data [read $fi 4]
    binary scan $data i mod_info_size

    set data [read $fi 4]
    binary scan $data i section_contribution_size

    set data [read $fi 4]
    binary scan $data i section_map_size

    set data [read $fi 4]
    binary scan $data i source_info_size

    seek $fi 24 current

    set mod_info [read $fi $mod_info_size]

    seek $fi [expr $section_contribution_size + $section_map_size] current

    set source_info [read $fi $source_info_size]

    close $fi

    # check source info substream

    set fi [open tmpdir/pdb3-source-info w]
    fconfigure $fi -translation binary
    puts -nonewline $fi $source_info
    close $fi

    set exp [file_contents "$srcdir/$subdir/pdb3-source-info.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb3-source-info"]

    if [string match $exp $got] {
	pass "Correct source info substream"
    } else {
	fail "Incorrect source info substream"
    }

    # check C13 info in first module

    set c13_info [extract_c13_info "tmpdir/pdb3.pdb" [string range $mod_info 0 63]]

    set fi [open tmpdir/pdb3-c13-info1 w]
    fconfigure $fi -translation binary
    puts -nonewline $fi $c13_info
    close $fi

    set exp [file_contents "$srcdir/$subdir/pdb3-c13-info1.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb3-c13-info1"]

    if [string match $exp $got] {
	pass "Correct C13 info for first module"
    } else {
	fail "Incorrect C13 info for first module"
    }

    # check C13 info in second module

    set fn1_end [string first \000 $mod_info 64]
    set fn2_end [string first \000 $mod_info [expr $fn1_end + 1]]

    set off [expr $fn2_end + 1]

    if { [expr $off % 4] != 0 } {
	set off [expr $off + 4 - ($off % 4)]
    }

    set c13_info [extract_c13_info "tmpdir/pdb3.pdb" [string range $mod_info $off [expr $off + 63]]]

    set fi [open tmpdir/pdb3-c13-info2 w]
    fconfigure $fi -translation binary
    puts -nonewline $fi $c13_info
    close $fi

    set exp [file_contents "$srcdir/$subdir/pdb3-c13-info2.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb3-c13-info2"]

    if [string match $exp $got] {
	pass "Correct C13 info for second module"
    } else {
	fail "Incorrect C13 info for second module"
    }
}

proc test5 { } {
    global as
    global ar
    global ld
    global objdump
    global srcdir
    global subdir

    if ![ld_assemble $as $srcdir/$subdir/pdb-types1a.s tmpdir/pdb-types1a.o] {
	unsupported "Build pdb-types1a.o"
	return
    }

    if ![ld_assemble $as $srcdir/$subdir/pdb-types1b.s tmpdir/pdb-types1b.o] {
	unsupported "Build pdb-types1b.o"
	return
    }

    if ![ld_link $ld "tmpdir/pdb-types1.exe" "--pdb=tmpdir/pdb-types1.pdb tmpdir/pdb-types1a.o tmpdir/pdb-types1b.o"] {
	unsupported "Create PE image with PDB file"
	return
    }

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types1.pdb 0002"]

    if ![string match "" $exec_output] {
	fail "Could not extract TPI stream"
	return
    } else {
	pass "Extracted TPI stream"
    }

    # check values in TPI header, and save anything interesting

    set fi [open tmpdir/0002]
    fconfigure $fi -translation binary

    seek $fi 8 current

    set data [read $fi 4]
    binary scan $data i first_type

    if { $first_type != 0x1000 } {
	fail "Incorrect first type value in TPI stream."
    } else {
	pass "Correct first type value in TPI stream."
    }

    set data [read $fi 4]
    binary scan $data i end_type

    # end_type is one greater than the last type in the stream
    if { $end_type != 0x102c } {
	fail "Incorrect end type value in TPI stream."
    } else {
	pass "Correct end type value in TPI stream."
    }

    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 2 current

    set data [read $fi 4]
    binary scan $data i hash_size

    if { $hash_size != 4 } {
	fail "Incorrect hash size in TPI stream."
    } else {
	pass "Correct hash size in TPI stream."
    }

    set data [read $fi 4]
    binary scan $data i num_buckets

    if { $num_buckets != 0x3ffff } {
	fail "Incorrect number of buckets in TPI stream."
    } else {
	pass "Correct number of buckets in TPI stream."
    }

    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-types1-typelist w]
    fconfigure $fi -translation binary
    puts -nonewline $fi $type_list
    close $fi

    # check type list

    set exp [file_contents "$srcdir/$subdir/pdb-types1-typelist.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types1-typelist"]
    if ![string match $exp $got] {
	fail "Incorrect type list in TPI stream."
    } else {
	pass "Correct type list in TPI 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-types1.pdb $index_str"]

    if ![string match "" $exec_output] {
	fail "Could not extract TPI hash stream."
    } else {
	pass "Extracted TPI 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-types1-hashlist w]
    fconfigure $fi -translation binary
    puts -nonewline $fi $hash_list
    close $fi

    set exp [file_contents "$srcdir/$subdir/pdb-types1-hashlist.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types1-hashlist"]
    if ![string match $exp $got] {
	fail "Incorrect hash list in TPI stream."
    } else {
	pass "Correct hash list in TPI stream."
    }

    # check skip list

    set fi [open tmpdir/pdb-types1-skiplist w]
    fconfigure $fi -translation binary
    puts -nonewline $fi $skip_list
    close $fi

    set exp [file_contents "$srcdir/$subdir/pdb-types1-skiplist.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types1-skiplist"]
    if ![string match $exp $got] {
	fail "Incorrect skip list in TPI stream."
    } else {
	pass "Correct skip list in TPI stream."
    }
}

proc test6 { } {
    global as
    global ar
    global ld
    global objdump
    global srcdir
    global subdir

    if ![ld_assemble $as $srcdir/$subdir/pdb-types2a.s tmpdir/pdb-types2a.o] {
	unsupported "Build pdb-types2a.o"
	return
    }

    if ![ld_assemble $as $srcdir/$subdir/pdb-types2b.s tmpdir/pdb-types2b.o] {
	unsupported "Build pdb-types2b.o"
	return
    }

    if ![ld_link $ld "tmpdir/pdb-types2.exe" "--pdb=tmpdir/pdb-types2.pdb tmpdir/pdb-types2a.o tmpdir/pdb-types2b.o"] {
	unsupported "Create PE image with PDB file"
	return
    }

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types2.pdb 0004"]

    if ![string match "" $exec_output] {
	fail "Could not extract IPI stream"
	return
    } else {
	pass "Extracted IPI stream"
    }

    # check values in IPI header, and save anything interesting

    set fi [open tmpdir/0004]
    fconfigure $fi -translation binary

    seek $fi 8 current

    set data [read $fi 4]
    binary scan $data i first_type

    if { $first_type != 0x1000 } {
	fail "Incorrect first type value in IPI stream."
    } else {
	pass "Correct first type value in IPI stream."
    }

    set data [read $fi 4]
    binary scan $data i end_type

    # end_type is one greater than the last type in the stream
    if { $end_type != 0x100f } {
	fail "Incorrect end type value in IPI stream."
    } else {
	pass "Correct end type value in IPI stream."
    }

    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 2 current

    set data [read $fi 4]
    binary scan $data i hash_size

    if { $hash_size != 4 } {
	fail "Incorrect hash size in IPI stream."
    } else {
	pass "Correct hash size in IPI stream."
    }

    set data [read $fi 4]
    binary scan $data i num_buckets

    if { $num_buckets != 0x3ffff } {
	fail "Incorrect number of buckets in IPI stream."
    } else {
	pass "Correct number of buckets in IPI stream."
    }

    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-types2-typelist w]
    fconfigure $fi -translation binary
    puts -nonewline $fi $type_list
    close $fi

    # check type list

    set exp [file_contents "$srcdir/$subdir/pdb-types2-typelist.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types2-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-types2.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-types2-hashlist w]
    fconfigure $fi -translation binary
    puts -nonewline $fi $hash_list
    close $fi

    set exp [file_contents "$srcdir/$subdir/pdb-types2-hashlist.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types2-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-types2-skiplist w]
    fconfigure $fi -translation binary
    puts -nonewline $fi $skip_list
    close $fi

    set exp [file_contents "$srcdir/$subdir/pdb-types2-skiplist.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types2-skiplist"]
    if ![string match $exp $got] {
	fail "Incorrect skip list in IPI stream."
    } else {
	pass "Correct skip list in IPI stream."
    }
}

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."
    }
}

proc test8 { } {
    global as
    global ar
    global ld
    global objdump
    global srcdir
    global subdir

    if ![ld_assemble $as $srcdir/$subdir/pdb-syms1a.s tmpdir/pdb-syms1a.o] {
	unsupported "Build pdb-syms1a.o"
	return
    }

    if ![ld_assemble $as $srcdir/$subdir/pdb-syms1b.s tmpdir/pdb-syms1b.o] {
	unsupported "Build pdb-syms1b.o"
	return
    }

    if ![ld_link $ld "tmpdir/pdb-syms1.exe" "--pdb=tmpdir/pdb-syms1.pdb tmpdir/pdb-syms1a.o tmpdir/pdb-syms1b.o"] {
	unsupported "Create PE image with PDB file"
	return
    }

    # get index of globals stream and records stream

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms1.pdb 0003"]

    if ![string match "" $exec_output] {
	fail "Could not extract DBI stream"
	return
    } else {
	pass "Extracted DBI stream"
    }

    set fi [open tmpdir/0003]
    fconfigure $fi -translation binary

    seek $fi 12
    set data [read $fi 2]
    binary scan $data s globals_index

    seek $fi 6 current
    set data [read $fi 2]
    binary scan $data s records_index

    seek $fi 2 current
    set data [read $fi 4]
    binary scan $data i mod_info_size

    seek $fi 36 current
    set mod_info [read $fi $mod_info_size]

    close $fi

    # get index of first and second module streams

    binary scan [string range $mod_info 34 35] s mod1_index

    set off 64

    set obj1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
    incr off [expr [string length $obj1] + 1]

    set ar1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
    incr off [expr [string length $ar1] + 1]

    if { [expr $off % 4] != 0 } {
	set off [expr $off + 4 - ($off % 4)]
    }

    incr off 34

    binary scan [string range $mod_info $off [expr $off + 1]] s mod2_index

    # check globals stream

    set index_str [format "%04x" $globals_index]

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms1.pdb $index_str"]

    if ![string match "" $exec_output] {
	fail "Could not extract globals stream"
	return
    } else {
	pass "Extracted globals stream"
    }

    set exp [file_contents "$srcdir/$subdir/pdb-syms1-globals.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]

    if [string match $exp $got] {
	pass "Correct globals stream"
    } else {
	fail "Incorrect globals stream"
    }

    # check records stream

    set index_str [format "%04x" $records_index]

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms1.pdb $index_str"]

    if ![string match "" $exec_output] {
	fail "Could not extract records stream"
	return
    } else {
	pass "Extracted records stream"
    }

    set exp [file_contents "$srcdir/$subdir/pdb-syms1-records.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]

    if [string match $exp $got] {
	pass "Correct records stream"
    } else {
	fail "Incorrect records stream"
    }

    # check symbols in first module

    set index_str [format "%04x" $mod1_index]

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms1.pdb $index_str"]

    if ![string match "" $exec_output] {
	fail "Could not extract first module's symbols"
	return
    } else {
	pass "Extracted first module's symbols"
    }

    set exp [file_contents "$srcdir/$subdir/pdb-syms1-symbols1.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]

    if [string match $exp $got] {
	pass "Correct symbols in first module's stream"
    } else {
	fail "Incorrect symbols in first module's stream"
    }

    # check symbols in second module

    set index_str [format "%04x" $mod2_index]

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms1.pdb $index_str"]

    if ![string match "" $exec_output] {
	fail "Could not extract second module's symbols"
	return
    } else {
	pass "Extracted second module's symbols"
    }

    set exp [file_contents "$srcdir/$subdir/pdb-syms1-symbols2.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]

    if [string match $exp $got] {
	pass "Correct symbols in second module's stream"
    } else {
	fail "Incorrect symbols in second module's stream"
    }
}

proc test9 { } {
    global as
    global ar
    global ld
    global objdump
    global srcdir
    global subdir

    if ![ld_assemble $as $srcdir/$subdir/pdb-syms2.s tmpdir/pdb-syms2.o] {
	unsupported "Build pdb-syms2.o"
	return
    }

    if ![ld_link $ld "tmpdir/pdb-syms2.exe" "--pdb=tmpdir/pdb-syms2.pdb tmpdir/pdb-syms2.o"] {
	unsupported "Create PE image with PDB file"
	return
    }

    # get index of module stream

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms2.pdb 0003"]

    if ![string match "" $exec_output] {
	fail "Could not extract DBI stream"
	return
    } else {
	pass "Extracted DBI stream"
    }

    set fi [open tmpdir/0003]
    fconfigure $fi -translation binary

    seek $fi 24
    set data [read $fi 4]
    binary scan $data i mod_info_size

    seek $fi 36 current
    set mod_info [read $fi $mod_info_size]

    close $fi

    binary scan [string range $mod_info 34 35] s module_index

    # check module records

    set index_str [format "%04x" $module_index]

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms2.pdb $index_str"]

    if ![string match "" $exec_output] {
	fail "Could not extract module symbols"
	return
    } else {
	pass "Extracted module symbols"
    }

    set exp [file_contents "$srcdir/$subdir/pdb-syms2-symbols1.d"]
    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]

    if [string match $exp $got] {
	pass "Correct symbols in module stream"
    } else {
	fail "Incorrect symbols in module stream"
    }

    # check linker symbols

    set off 64

    set obj1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
    incr off [expr [string length $obj1] + 1]

    set ar1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
    incr off [expr [string length $ar1] + 1]

    if { [expr $off % 4] != 0 } {
	set off [expr $off + 4 - ($off % 4)]
    }

    incr off 34

    binary scan [string range $mod_info $off [expr $off + 1]] s linker_syms_index

    set index_str [format "%04x" $linker_syms_index]

    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms2.pdb $index_str"]

    if ![string match "" $exec_output] {
	fail "Could not extract linker symbols"
	return
    } else {
	pass "Extracted linker symbols"
    }

    set syms [file_contents "tmpdir/$index_str"]

    # check S_OBJNAME

    set off 4
    binary scan [string range $syms $off [expr $off + 1]] s sym_len
    binary scan [string range $syms [expr $off + 2] [expr $off + 3]] s sym_type

    if { $sym_type != 0x1101 } {
	fail "First linker symbol was not S_OBJNAME"
    } else {
	pass "First linker symbol was S_OBJNAME"

	set linker_fn [string range $syms [expr $off + 8] [expr [string first \000 $syms [expr $off + 8]] - 1]]

	if ![string equal $linker_fn "* Linker *"] {
	    fail "Incorrect linker object name"
	} else {
	    pass "Correct linker object name"
	}
    }

    incr off [expr $sym_len + 2]

    # check S_COMPILE3

    binary scan [string range $syms $off [expr $off + 1]] s sym_len
    binary scan [string range $syms [expr $off + 2] [expr $off + 3]] s sym_type

    if { $sym_type != 0x113c } {
	fail "Second linker symbol was not S_COMPILE3"
    } else {
	pass "Second linker symbol was S_COMPILE3"
    }

    incr off [expr $sym_len + 2]

    # check S_ENVBLOCK

    binary scan [string range $syms $off [expr $off + 1]] s sym_len
    binary scan [string range $syms [expr $off + 2] [expr $off + 3]] s sym_type

    if { $sym_type != 0x113d } {
	fail "Third linker symbol was not S_ENVBLOCK"
    } else {
	pass "Third linker symbol was S_ENVBLOCK"
    }
}

test1
test2
test3
test4
test5
test6
test7
test8
test9