# Copyright 2002
# 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 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  

# Please email any bugs, comments, and/or additions to this file to:
# bug-gdb@prep.ai.mit.edu

# This file was written by Jason Molenda (jmolenda@apple.com)

if $tracelevel then {
        strace $tracelevel
}


# Test library slides.  On MacOS X, most system libraries have set
# addresses where they will load.  Our libc-like library, libSystem, loads
# at 0x90000000 or somesuch, for instance.  If another shared library tries
# to go at that same address, it gets shifted to another addr, and gdb needs
# to (1) notice that shift, (2) update all the symbols and breakpoints.


set timeout 30

set prms_id 0
set bug_id 0

set testfile "slipnslide"
set binfile ${objdir}/${subdir}/${testfile}

if  { [gdb_compile "${srcdir}/${subdir}/slipnslide.c" "${binfile}" executable {debug}] != "" } {
     gdb_suppress_entire_file "Testcase compile failed, so all tests in this file will automatically fail."
}

# Start with a fresh gdb

gdb_exit
gdb_start
gdb_reinitialize_dir $srcdir/$subdir
gdb_load ${binfile}

# Figure out where libSystem normally loads, its full pathname, and its short
# name.
send_gdb "info sharedlibrary\n"
gdb_expect {
   -re ".* (libSystem...dylib) *(-|$hex) *- *init Y Y \"(.*libSystem.B.dylib)\" at ($hex)( \\(offset (0x0)\\))?\[\r\n\].*" {
      pass "get libSystem pre-startup load address"
      set libsystem_shortname $expect_out(1,string)
      set libsystem_full_pathname $expect_out(3,string)
      set libsystem_load_addr $expect_out(4,string)
  }
  -re "$gdb_prompt $" {
      fail "get libSystem pre-startup load address"
  }
  timeout {
      fail "(timeout) get libSystem pre-startup load address" 
  }
}

# See where printf() normally loads.
send_gdb "p printf\n"
gdb_expect {
  -re ".*\\\$1 = .* ($hex) <printf>.*$gdb_prompt $" {
    pass "get printf pre-start load address"
    set printf_address $expect_out(1,string)
  }
  -re "$gdb_prompt $" {
      fail "get printf pre-start load address"
  }
  timeout {
      fail "(timeout) get printf pre-start load address" 
  }
}

# See where puts() normally loads.
send_gdb "p puts\n"
gdb_expect {
  -re ".*\\\$2 = .* ($hex) <puts>.*$gdb_prompt $" {
    pass "get puts pre-start load address"
    set libsystem_puts_address $expect_out(1,string)
  }
  -re "$gdb_prompt $" {
      fail "get puts pre-start load address"
  }
  timeout {
      fail "(timeout) get puts pre-start load address" 
  }
}

if ![runto_main] then { 
  fail "slipnslide tests suppressed"
  return -1
}

# Verify that nothing has slid with the default case.
send_gdb "info sharedlibrary\n"
gdb_expect {
  -re ".* $decimal $libsystem_shortname *- $libsystem_load_addr *dyld Y Y \"$libsystem_full_pathname\" at $libsystem_load_addr \\(offset 0x0\\).*$gdb_prompt $" {
    pass "default libSystem didn't slide"
  }
  -re "$gdb_prompt $" {
      fail "default libSystem didn't slide"
  }
  timeout {
      fail "(timeout) default libSystem didn't slide"
  }
}

# Verify that printf is where it should be.
gdb_test "p printf" "\\\$3 = .* $printf_address <printf>"  "printf is where it should be on a non-slide"

gdb_exit



##
## Now we verify all that via MI just for fun.
## 

load_lib mi-support.exp
set MIFLAGS "-i=mi1"

mi_gdb_start
mi_delete_breakpoints
mi_gdb_reinitialize_dir $srcdir/$subdir
mi_gdb_load ${binfile}

mi_gdb_test "10-file-sharedlibrary-info" "10\\^done.*shlib-info=\\\[num=\"$decimal\",name=\"$libsystem_shortname\",kind=\"-\",dyld-addr=\"-\",reason=\"init\",requested-state=\"Y\",state=\"Y\",path=\"$libsystem_full_pathname\",slide=\"\",loaded_addr=\"$libsystem_load_addr\",prefix=\"\"\\\].*" "verify pre-start libsystem via mi1"

mi_gdb_test "20-break-insert main" "20\\^done,bkpt=.*"
mi_gdb_test "30-gdb-set stop-on-solib-events 1" "30\\^done" "gdb shows solib events"

send_gdb "40-exec-run\n"
gdb_expect {
  -re "40\\^running\[^(\]*=shlibs-updated\[^(\]*$mi_gdb_prompt" {
    pass "first part of run startup.."
  }
  timeout {
    fail "(timeout) first part of run startup.. in mi1"
  }
}

gdb_expect {
  -re ".*\\\*stopped,reason=\"shlib-event\".*$mi_gdb_prompt$" {
    pass "catch shlib-event during startup"
  }
  timeout {
    fail "(timeout) catch shlib-event during startup in mi1"
  }
}

mi_gdb_test "50-file-sharedlibrary-info" "50\\^done.*shlib-info=\\\[num=\"$decimal\",name=\"$libsystem_shortname\",kind=\"-\",dyld-addr=\"$libsystem_load_addr\",reason=\"dyld\",requested-state=\"Y\",state=\"Y\",path=\"$libsystem_full_pathname\",loaded_addr=\"$libsystem_load_addr\",slide=\"0x0\",prefix=\"\"\\\].*" "verify pre-start libsystem via mi1"
mi_gdb_exit




##
## Now create a shared library that conflicts with libSystem.
## 


set testlibfile "libmylib"
set libbinfile ${objdir}/${subdir}/${testlibfile}.dylib

set additional_flags "additional_flags=-dynamiclib -seg1addr $libsystem_load_addr"
if  { [gdb_compile "${srcdir}/${subdir}/${testlibfile}.c" "${libbinfile}" executable [list debug $additional_flags]] != "" } {
     gdb_suppress_entire_file "Testcase compile failed, so all tests in this file will automatically fail."
}

##
## Mix it all up and see what happens..
##

gdb_start
gdb_reinitialize_dir $srcdir/$subdir
gdb_test "set env DYLD_INSERT_LIBRARIES $libbinfile" ""
gdb_load ${binfile}

# Verify that libSystem and mylib are both going to try loading at the same addr.
send_gdb "info sharedlibrary\n"
gdb_expect {
  -re ".* $decimal ${testlibfile}.dylib *- *- *init Y Y \"$libbinfile\" at $libsystem_load_addr.* $decimal $libsystem_shortname *- *- *init Y Y \"$libsystem_full_pathname\" at $libsystem_load_addr.*$gdb_prompt $" {
    pass "libmylib and libsystem will both try to load at same addr"
  }
  -re "$gdb_prompt $" {
      fail "libmylib and libsystem will both try to load at same addr"
  }
  timeout {
      fail "(timeout) libmylib and libsystem will both try to load at same addr"
  }
}

# Get address of function 'foo' in mylib, pre-slid

# See where foo() normally loads.
send_gdb "p foo\n"
gdb_expect {
  -re ".*\\\$1 = .* ($hex) <foo>.*$gdb_prompt $" {
    pass "get foo pre-start load address"
    set foo_address $expect_out(1,string)
    
  }
  -re "$gdb_prompt $" {
      fail "get foo pre-start load address"
      set foo_address -1
  }
  timeout {
      fail "(timeout) get foo pre-start load address" 
      set foo_address -1
  }
}

# See where puts() thinks it's going to load this time..
send_gdb "p puts\n"
gdb_expect {
  -re ".*\\\$2 = .* ($hex) <puts>.*$gdb_prompt $" {
    pass "get puts pre-start load address"
    set puts_address $expect_out(1,string)
  }
  -re "$gdb_prompt $" {
      fail "get puts pre-start load address"
      set puts_address -1
  }
  timeout {
      fail "(timeout) get puts pre-start load address" 
      set puts_address -1
  }
}





# Set three breakpoints:  One on a function only in mylib.dylib, one
# that exists in both mylib.dylib and libSystem, and one in only libSystem.

send_gdb "b foo\n"
gdb_expect {
  -re "Breakpoint 1 at ($hex): file .*$gdb_prompt $" {
    set foo_breakpoint_address $expect_out(1,string)
  }
  timeout { fail "could not set breakpoint on foo" }
}

send_gdb "b puts\n"
gdb_expect {
  -re "Breakpoint 2 at ($hex): file .*$gdb_prompt $" {
    set puts_breakpoint_address $expect_out(1,string)
  }
  timeout { fail "could not set breakpoint on puts" }
}

send_gdb "b printf\n"
gdb_expect {
  -re "Breakpoint 3 at ($hex).*$gdb_prompt $" {
    set printf_breakpoint_address $expect_out(1,string)
  }
  timeout { fail "could not set breakpoint on printf" }
}

set foo_offset_pre_start [expr $foo_address - $libsystem_load_addr]
set puts_offset_pre_start [expr $puts_address - $libsystem_load_addr]
set foo_breakpoint_offset_pre_start [expr $foo_breakpoint_address - $libsystem_load_addr]
set puts_breakpoint_offset_pre_start [expr $puts_breakpoint_address - $libsystem_load_addr]
set printf_breakpoint_offset_pre_start [expr $printf_breakpoint_address - $libsystem_load_addr]



gdb_test "b main" "Breakpoint 4 at .*"
gdb_test "run" "Starting program:.*"

# Verify that mydlib slid like it should have and libSystem stayed put.
send_gdb "info sharedlibrary\n"
gdb_expect {
  -re ".* $decimal ${testlibfile}.dylib *- ($hex) *dyld Y Y \"$libbinfile\" at ($hex) \\(offset (0x\[^0\]\[0-9a-fA-F\]+)\\).* $decimal $libsystem_shortname *- $libsystem_load_addr *dyld Y Y \"$libsystem_full_pathname\" at $libsystem_load_addr \\(offset 0x0\\).*$gdb_prompt $" {
    set mylib_slid_addr $expect_out(1,string)
    set mylib_slid_addr_duplicate $expect_out(1,string)
    set mylib_slid_offset $expect_out(2,string)
    pass "mylib slid, libsystem stayed put"
    if {$mylib_slid_addr != $libsystem_load_addr} { 
      pass "mylib is at address other than libsystem" 
    } else {
      fail "mylib is at address other than libsystem"
    }
    if {$mylib_slid_addr == $mylib_slid_addr_duplicate} {
      pass "load addresses from info sharedlibrary match"
    } else {
      fail "load addresses from info sharedlibrary match"
    }
  }
  -re "$gdb_prompt $" {
      fail "mylib slid, libsystem stayed put"
      set mylib_slid_addr -1
      set mylib_slid_addr_duplicate -1
      set mylib_slid_offset -1
  }
  timeout {
      fail "(timeout) mylib slid, libsystem stayed put"
      set mylib_slid_addr -1
      set mylib_slid_addr_duplicate -1
      set mylib_slid_offset -1
  }
}




# See where foo actually loaded.
send_gdb "p foo\n"
gdb_expect {
  -re ".*\\\$$decimal = .* ($hex) <foo>.*$gdb_prompt $" {
    pass "get foo post-start load address"
    set foo_loaded_address $expect_out(1,string)
  }
  -re "$gdb_prompt $" {
      fail "get foo pre-start load address"
  }
  timeout {
      fail "(timeout) get foo pre-start load address" 
  }
}
if {$foo_address != $foo_loaded_address} {
  pass "address of foo must change when library slid"
} else {
  fail "address of foo must change when library slid"
} 

# See where puts actually loaded.
send_gdb "p puts\n"
gdb_expect {
  -re ".*\\\$$decimal = .* ($hex) <puts>.*$gdb_prompt $" {
    pass "get puts post-start load address"
    set puts_loaded_address $expect_out(1,string)
  }
  -re "$gdb_prompt $" {
      fail "get puts pre-start load address"
  }
  timeout {
      fail "(timeout) get puts pre-start load address" 
  }
}
if {$puts_address != $puts_loaded_address} {
  pass "address of puts must change when library slid"
} else {
  fail "address of puts must change when library slid"
} 
if {$puts_address != $libsystem_puts_address || $puts_loaded_address != $libsystem_puts_address} {
  pass "evaluation of puts is mylib version, not libsystem"
} else {
  fail "evaluation of puts is mylib version, not libsystem"
}

# See where printf actually loaded.
send_gdb "p printf\n"
gdb_expect {
  -re ".*\\\$$decimal = .* ($hex) <printf>.*$gdb_prompt $" {
    pass "get printf post-start load address"
    set printf_loaded_address $expect_out(1,string)
  }
  -re "$gdb_prompt $" {
      fail "get printf pre-start load address"
  }
  timeout {
      fail "(timeout) get printf pre-start load address" 
  }
}
if {$printf_address == $printf_loaded_address} {
  pass "address of printf didn't changed when user library slid"
} else {
  fail "address of printf didn't changed when user library slid"
}

set foo_offset_slid [expr $foo_loaded_address - $mylib_slid_addr]
set puts_offset_slid [expr $puts_loaded_address - $mylib_slid_addr]
set printf_offset_slid [expr $printf_loaded_address - $libsystem_load_addr]

if {$foo_offset_pre_start == $foo_offset_slid} {
  pass "offset of foo in mylib.dylib stayed same after slide."
} else {
  fail "offset of foo in mylib.dylib stayed same after slide."
}

if {$puts_offset_pre_start == $puts_offset_slid} {
  pass "offset of puts in mylib.dylib stayed same after slide."
} else {
  fail "offset of puts in mylib.dylib stayed same after slide."
}


# See that 'foo' breakpoint moved.
send_gdb "info br 1\n"
gdb_expect {
  -re ".*$decimal *breakpoint *keep y *($hex) in foo.*$gdb_prompt $" {
    set foo_slid_address $expect_out(1,string)
  }
  -re "$gdb_prompt $" {
    set foo_slid_address -1
    fail "could not examine foo breakpoint"
  }
  timeout {
    set foo_slid_address -1
    fail "(timeout) could not examine foo breakpoint"
  }
}
if {$foo_slid_address != $foo_breakpoint_address } { 
  pass "pass breakpoint on foo slid with library"
} else {
  fail "pass breakpoint on foo slid with library"
}

# See that 'puts' breakpoint moved.
send_gdb "info br 2\n"
gdb_expect {
  -re ".*$decimal *breakpoint *keep y *($hex) in puts.*$gdb_prompt $" {
    set puts_slid_address $expect_out(1,string)
  }
  -re "$gdb_prompt $" {
    set puts_slid_address -1
    fail "could not examine puts breakpoint"
  }
  timeout {
    set puts_slid_address -1
    fail "(timeout) could not examine puts breakpoint"
  }
}

if {$puts_slid_address != $puts_breakpoint_address } { 
  pass "pass breakpoint on puts slid with library"
} else {
  fail "pass breakpoint on puts slid with library"
}

# See that 'printf' breakpoint didn't moved.
send_gdb "info br 3\n"
gdb_expect {
  -re ".*$decimal *breakpoint *keep y *($hex) <printf.*$gdb_prompt $" {
    set printf_slid_address $expect_out(1,string)
  }
  -re "$gdb_prompt $" {
    set printf_slid_address -1
    fail "could not examine printf breakpoint"
  }
  timeout {
    set printf_slid_address -1
    fail "(timeout) could not examine printf breakpoint"
  }
}

if {$printf_slid_address == $printf_breakpoint_address} { 
  pass "pass breakpoint on printf in Libsystem didn't slide"
} else {
  fail "pass breakpoint on printf in Libsystem didn't slide"
}

gdb_test "dis 3" ""
gdb_test "continue" "Breakpoint 1, foo.*libmylib.c:.*" "continue to slid breakpoint on foo"


gdb_exit
return 0
