Home | History | Annotate | Line # | Download | only in gdb.python
      1  1.1.1.2  christos # Copyright (C) 2021-2024 Free Software Foundation, Inc.
      2      1.1  christos 
      3      1.1  christos # This program is free software; you can redistribute it and/or modify
      4      1.1  christos # it under the terms of the GNU General Public License as published by
      5      1.1  christos # the Free Software Foundation; either version 3 of the License, or
      6      1.1  christos # (at your option) any later version.
      7      1.1  christos #
      8      1.1  christos # This program is distributed in the hope that it will be useful,
      9      1.1  christos # but WITHOUT ANY WARRANTY; without even the implied warranty of
     10      1.1  christos # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11      1.1  christos # GNU General Public License for more details.
     12      1.1  christos #
     13      1.1  christos # You should have received a copy of the GNU General Public License
     14      1.1  christos # along with this program.  If not, see <http://www.gnu.org/licenses/>.
     15      1.1  christos 
     16      1.1  christos # This file is part of the GDB testsuite.  It validates the Python
     17      1.1  christos # disassembler API.
     18      1.1  christos 
     19      1.1  christos load_lib gdb-python.exp
     20      1.1  christos 
     21  1.1.1.2  christos require allow_python_tests
     22  1.1.1.2  christos 
     23      1.1  christos standard_testfile
     24      1.1  christos 
     25      1.1  christos if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} "debug"] } {
     26      1.1  christos     return -1
     27      1.1  christos }
     28      1.1  christos 
     29      1.1  christos if {![runto_main]} {
     30      1.1  christos     fail "can't run to main"
     31      1.1  christos     return 0
     32      1.1  christos }
     33      1.1  christos 
     34      1.1  christos set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
     35      1.1  christos 
     36      1.1  christos gdb_test "source ${pyfile}" "Python script imported" \
     37      1.1  christos          "import python scripts"
     38      1.1  christos 
     39      1.1  christos gdb_breakpoint [gdb_get_line_number "Break here."]
     40      1.1  christos gdb_continue_to_breakpoint "Break here."
     41      1.1  christos 
     42      1.1  christos set curr_pc [get_valueof "/x" "\$pc" "*unknown*"]
     43      1.1  christos 
     44      1.1  christos gdb_test_no_output "python current_pc = ${curr_pc}"
     45      1.1  christos 
     46      1.1  christos # The current pc will be something like 0x1234 with no leading zeros.
     47      1.1  christos # However, in the disassembler output addresses are padded with zeros.
     48      1.1  christos # This substitution changes 0x1234 to 0x0*1234, which can then be used
     49      1.1  christos # as a regexp in the disassembler output matching.
     50      1.1  christos set curr_pc_pattern [string replace ${curr_pc} 0 1 "0x0*"]
     51      1.1  christos 
     52      1.1  christos # Grab the name of the current architecture, this is used in the tests
     53      1.1  christos # patterns below.
     54      1.1  christos set curr_arch [get_python_valueof "gdb.selected_inferior().architecture().name()" "*unknown*"]
     55      1.1  christos 
     56      1.1  christos # Helper proc that removes all registered disassemblers.
     57      1.1  christos proc py_remove_all_disassemblers {} {
     58      1.1  christos     gdb_test_no_output "python remove_all_python_disassemblers()"
     59      1.1  christos }
     60      1.1  christos 
     61      1.1  christos # A list of test plans.  Each plan is a list of two elements, the
     62      1.1  christos # first element is the name of a class in py-disasm.py, this is a
     63      1.1  christos # disassembler class.  The second element is a pattern that should be
     64      1.1  christos # matched in the disassembler output.
     65      1.1  christos #
     66      1.1  christos # Each different disassembler tests some different feature of the
     67      1.1  christos # Python disassembler API.
     68  1.1.1.2  christos set nop "(nop|nop\t0|[string_to_regexp nop\t{0}])"
     69      1.1  christos set unknown_error_pattern "unknown disassembler error \\(error = -1\\)"
     70      1.1  christos set addr_pattern "\r\n=> ${curr_pc_pattern} <\[^>\]+>:\\s+"
     71      1.1  christos set base_pattern "${addr_pattern}${nop}"
     72  1.1.1.2  christos 
     73  1.1.1.2  christos # Helper proc to format a Python exception of TYPE with MSG.
     74  1.1.1.2  christos proc make_exception_pattern { type msg } {
     75  1.1.1.2  christos     return "${::addr_pattern}Python Exception <class '$type'>: $msg\r\n\r\n${::unknown_error_pattern}"
     76  1.1.1.2  christos }
     77  1.1.1.2  christos 
     78  1.1.1.2  christos # Helper proc to build a pattern for the text Python emits when a
     79  1.1.1.2  christos # function argument is missing.  This string changed in Python 3.7 and
     80  1.1.1.2  christos # later.  NAME is the parameter name, and POS is its integer position
     81  1.1.1.2  christos # in the argument list.
     82  1.1.1.2  christos proc missing_arg_pattern { name pos } {
     83  1.1.1.2  christos     set pattern_1 "function missing required argument '$name' \\(pos $pos\\)"
     84  1.1.1.2  christos     set pattern_2 "Required argument '$name' \\(pos $pos\\) not found"
     85  1.1.1.2  christos     return "(?:${pattern_1}|${pattern_2})"
     86  1.1.1.2  christos }
     87  1.1.1.2  christos 
     88      1.1  christos set test_plans \
     89      1.1  christos     [list \
     90      1.1  christos 	 [list "" "${base_pattern}\r\n.*"] \
     91      1.1  christos 	 [list "GlobalNullDisassembler" "${base_pattern}\r\n.*"] \
     92  1.1.1.2  christos 	 [list "ShowInfoRepr" "${base_pattern}\\s+## <gdb.disassembler.DisassembleInfo address=$hex architecture=\[^>\]+>\r\n.*"] \
     93  1.1.1.2  christos 	 [list "ShowInfoSubClassRepr" "${base_pattern}\\s+## <MyInfo address=$hex architecture=\[^>\]+>\r\n.*"] \
     94  1.1.1.2  christos 	 [list "ShowResultRepr" "${base_pattern}\\s+## <gdb.disassembler.DisassemblerResult length=$decimal string=\"\[^\r\n\]+\">\r\n.*"] \
     95  1.1.1.2  christos 	 [list "ShowResultStr" "${base_pattern}\\s+## ${nop}\r\n.*"] \
     96      1.1  christos 	 [list "GlobalPreInfoDisassembler" "${base_pattern}\\s+## ad = $hex, ar = ${curr_arch}\r\n.*"] \
     97      1.1  christos 	 [list "GlobalPostInfoDisassembler" "${base_pattern}\\s+## ad = $hex, ar = ${curr_arch}\r\n.*"] \
     98      1.1  christos 	 [list "GlobalReadDisassembler" "${base_pattern}\\s+## bytes =( $hex)+\r\n.*"] \
     99      1.1  christos 	 [list "GlobalAddrDisassembler" "${base_pattern}\\s+## addr = ${curr_pc_pattern} <\[^>\]+>\r\n.*"] \
    100      1.1  christos 	 [list "GdbErrorEarlyDisassembler" "${addr_pattern}GdbError instead of a result\r\n${unknown_error_pattern}"] \
    101      1.1  christos 	 [list "RuntimeErrorEarlyDisassembler" "${addr_pattern}Python Exception <class 'RuntimeError'>: RuntimeError instead of a result\r\n\r\n${unknown_error_pattern}"] \
    102      1.1  christos 	 [list "GdbErrorLateDisassembler" "${addr_pattern}GdbError after builtin disassembler\r\n${unknown_error_pattern}"] \
    103      1.1  christos 	 [list "RuntimeErrorLateDisassembler" "${addr_pattern}Python Exception <class 'RuntimeError'>: RuntimeError after builtin disassembler\r\n\r\n${unknown_error_pattern}"] \
    104      1.1  christos 	 [list "MemoryErrorEarlyDisassembler" "${base_pattern}\\s+## AFTER ERROR\r\n.*"] \
    105      1.1  christos 	 [list "MemoryErrorLateDisassembler" "${addr_pattern}Cannot access memory at address ${curr_pc_pattern}"] \
    106      1.1  christos 	 [list "RethrowMemoryErrorDisassembler" "${addr_pattern}Cannot access memory at address $hex"] \
    107      1.1  christos 	 [list "ReadMemoryMemoryErrorDisassembler" "${addr_pattern}Cannot access memory at address ${curr_pc_pattern}"] \
    108      1.1  christos 	 [list "ReadMemoryGdbErrorDisassembler" "${addr_pattern}read_memory raised GdbError\r\n${unknown_error_pattern}"] \
    109  1.1.1.2  christos 	 [list "ReadMemoryRuntimeErrorDisassembler" \
    110  1.1.1.2  christos 	      [make_exception_pattern "RuntimeError" \
    111  1.1.1.2  christos 		   "read_memory raised RuntimeError"]] \
    112      1.1  christos 	 [list "ReadMemoryCaughtMemoryErrorDisassembler" "${addr_pattern}${nop}\r\n.*"] \
    113      1.1  christos 	 [list "ReadMemoryCaughtGdbErrorDisassembler" "${addr_pattern}${nop}\r\n.*"] \
    114      1.1  christos 	 [list "ReadMemoryCaughtRuntimeErrorDisassembler" "${addr_pattern}${nop}\r\n.*"] \
    115  1.1.1.2  christos 	 [list "MemorySourceNotABufferDisassembler" \
    116  1.1.1.2  christos 	      [make_exception_pattern "TypeError" \
    117  1.1.1.2  christos 		   "Result from read_memory is not a buffer"]] \
    118  1.1.1.2  christos 	 [list "MemorySourceBufferTooLongDisassembler" \
    119  1.1.1.2  christos 	      [make_exception_pattern "ValueError" \
    120  1.1.1.2  christos 		   "Buffer returned from read_memory is sized $decimal instead of the expected $decimal"]] \
    121  1.1.1.2  christos 	 [list "ResultOfWrongType" \
    122  1.1.1.2  christos 	      [make_exception_pattern "TypeError" \
    123  1.1.1.2  christos 		   "Result is not a DisassemblerResult."]] \
    124  1.1.1.2  christos 	 [list "ErrorCreatingTextPart_NoArgs" \
    125  1.1.1.2  christos 	      [make_exception_pattern "TypeError" \
    126  1.1.1.2  christos 		   [missing_arg_pattern "style" 1]]] \
    127  1.1.1.2  christos 	 [list "ErrorCreatingAddressPart_NoArgs" \
    128  1.1.1.2  christos 	      [make_exception_pattern "TypeError" \
    129  1.1.1.2  christos 		   [missing_arg_pattern "address" 1]]] \
    130  1.1.1.2  christos 	 [list "ErrorCreatingTextPart_NoString" \
    131  1.1.1.2  christos 	      [make_exception_pattern "TypeError" \
    132  1.1.1.2  christos 		   [missing_arg_pattern "string" 2]]] \
    133  1.1.1.2  christos 	 [list "ErrorCreatingTextPart_NoStyle" \
    134  1.1.1.2  christos 	      [make_exception_pattern "TypeError" \
    135  1.1.1.2  christos 		   [missing_arg_pattern "style" 1]]] \
    136  1.1.1.2  christos 	 [list "All_Text_Part_Styles" "${addr_pattern}p1p2p3p4p5p6p7p8p9p10\r\n.*"] \
    137  1.1.1.2  christos 	 [list "ErrorCreatingTextPart_StringAndParts" \
    138  1.1.1.2  christos 	      [make_exception_pattern "ValueError" \
    139  1.1.1.2  christos 		  "Cannot use 'string' and 'parts' when creating gdb\\.disassembler\\.DisassemblerResult\\."]] \
    140  1.1.1.2  christos 	 [list "Build_Result_Using_All_Parts" \
    141  1.1.1.2  christos 	      "${addr_pattern}fake\treg, ${curr_pc_pattern}(?: <\[^>\]+>)?, 123\r\n.*"] \
    142  1.1.1.2  christos 	]
    143      1.1  christos 
    144      1.1  christos # Now execute each test plan.
    145      1.1  christos foreach plan $test_plans {
    146      1.1  christos     set global_disassembler_name [lindex $plan 0]
    147      1.1  christos     set expected_pattern [lindex $plan 1]
    148      1.1  christos 
    149      1.1  christos     with_test_prefix "global_disassembler=${global_disassembler_name}" {
    150      1.1  christos 	# Remove all existing disassemblers.
    151      1.1  christos 	py_remove_all_disassemblers
    152      1.1  christos 
    153      1.1  christos 	# If we have a disassembler to load, do it now.
    154      1.1  christos 	if { $global_disassembler_name != "" } {
    155      1.1  christos 	    gdb_test_no_output "python add_global_disassembler($global_disassembler_name)"
    156      1.1  christos 	}
    157      1.1  christos 
    158      1.1  christos 	# Disassemble test, and check the disassembler output.
    159      1.1  christos 	gdb_test "disassemble test" $expected_pattern
    160      1.1  christos     }
    161      1.1  christos }
    162      1.1  christos 
    163      1.1  christos # Check some errors relating to DisassemblerResult creation.
    164      1.1  christos with_test_prefix "DisassemblerResult errors" {
    165      1.1  christos     gdb_test "python gdb.disassembler.DisassemblerResult(0, 'abc')" \
    166      1.1  christos 	[multi_line \
    167  1.1.1.2  christos 	     "ValueError.*: Length must be greater than 0." \
    168  1.1.1.2  christos 	     "Error occurred in Python.*"]
    169      1.1  christos     gdb_test "python gdb.disassembler.DisassemblerResult(-1, 'abc')" \
    170      1.1  christos 	[multi_line \
    171  1.1.1.2  christos 	     "ValueError.*: Length must be greater than 0." \
    172  1.1.1.2  christos 	     "Error occurred in Python.*"]
    173      1.1  christos     gdb_test "python gdb.disassembler.DisassemblerResult(1, '')" \
    174      1.1  christos 	[multi_line \
    175  1.1.1.2  christos 	     "ValueError.*: String must not be empty.*" \
    176  1.1.1.2  christos 	     "Error occurred in Python.*"]
    177      1.1  christos }
    178      1.1  christos 
    179      1.1  christos # Check that the architecture specific disassemblers can override the
    180      1.1  christos # global disassembler.
    181      1.1  christos #
    182      1.1  christos # First, register a global disassembler, and check it is in place.
    183      1.1  christos with_test_prefix "GLOBAL tagging disassembler" {
    184      1.1  christos     py_remove_all_disassemblers
    185      1.1  christos     gdb_test_no_output "python gdb.disassembler.register_disassembler(TaggingDisassembler(\"GLOBAL\"), None)"
    186      1.1  christos     gdb_test "disassemble test" "${base_pattern}\\s+## tag = GLOBAL\r\n.*"
    187      1.1  christos }
    188      1.1  christos 
    189      1.1  christos # Now register an architecture specific disassembler, and check it
    190      1.1  christos # overrides the global disassembler.
    191      1.1  christos with_test_prefix "LOCAL tagging disassembler" {
    192      1.1  christos     gdb_test_no_output "python gdb.disassembler.register_disassembler(TaggingDisassembler(\"LOCAL\"), \"${curr_arch}\")"
    193      1.1  christos     gdb_test "disassemble test" "${base_pattern}\\s+## tag = LOCAL\r\n.*"
    194      1.1  christos }
    195      1.1  christos 
    196      1.1  christos # Now remove the architecture specific disassembler, and check that
    197      1.1  christos # the global disassembler kicks back in.
    198      1.1  christos with_test_prefix "GLOBAL tagging disassembler again" {
    199      1.1  christos     gdb_test_no_output "python gdb.disassembler.register_disassembler(None, \"${curr_arch}\")"
    200      1.1  christos     gdb_test "disassemble test" "${base_pattern}\\s+## tag = GLOBAL\r\n.*"
    201      1.1  christos }
    202      1.1  christos 
    203      1.1  christos # Check that a DisassembleInfo becomes invalid after the call into the
    204      1.1  christos # disassembler.
    205      1.1  christos with_test_prefix "DisassembleInfo becomes invalid" {
    206      1.1  christos     py_remove_all_disassemblers
    207      1.1  christos     gdb_test_no_output "python add_global_disassembler(GlobalCachingDisassembler)"
    208      1.1  christos     gdb_test "disassemble test" "${base_pattern}\\s+## CACHED\r\n.*"
    209      1.1  christos     gdb_test "python GlobalCachingDisassembler.check()" "PASS"
    210      1.1  christos }
    211      1.1  christos 
    212      1.1  christos # Test the memory source aspect of the builtin disassembler.
    213      1.1  christos with_test_prefix "memory source api" {
    214      1.1  christos     py_remove_all_disassemblers
    215      1.1  christos     gdb_test_no_output "python analyzing_disassembler = add_global_disassembler(AnalyzingDisassembler)"
    216      1.1  christos     gdb_test "disassemble test" "${base_pattern}\r\n.*"
    217      1.1  christos     gdb_test "python analyzing_disassembler.find_replacement_candidate()" \
    218      1.1  christos 	"Replace from $hex to $hex with NOP"
    219      1.1  christos     gdb_test "disassemble test" "${base_pattern}\r\n.*" \
    220      1.1  christos 	"second disassembler pass"
    221      1.1  christos     gdb_test "python analyzing_disassembler.check()" \
    222      1.1  christos 	"PASS"
    223      1.1  christos }
    224      1.1  christos 
    225      1.1  christos # Test the 'maint info python-disassemblers command.
    226      1.1  christos with_test_prefix "maint info python-disassemblers" {
    227      1.1  christos     py_remove_all_disassemblers
    228      1.1  christos     gdb_test "maint info python-disassemblers" "No Python disassemblers registered\\." \
    229      1.1  christos 	"list disassemblers, none registered"
    230      1.1  christos     gdb_test_no_output "python disasm = add_global_disassembler(BuiltinDisassembler)"
    231      1.1  christos     gdb_test "maint info python-disassemblers" \
    232      1.1  christos 	[multi_line \
    233      1.1  christos 	     "Architecture\\s+Disassember Name" \
    234      1.1  christos 	     "GLOBAL\\s+BuiltinDisassembler\\s+\\(Matches current architecture\\)"] \
    235      1.1  christos 	"list disassemblers, single global disassembler"
    236      1.1  christos     gdb_test_no_output "python arch = gdb.selected_inferior().architecture().name()"
    237      1.1  christos     gdb_test_no_output "python gdb.disassembler.register_disassembler(disasm, arch)"
    238      1.1  christos     gdb_test "maint info python-disassemblers" \
    239      1.1  christos 	[multi_line \
    240      1.1  christos 	     "Architecture\\s+Disassember Name" \
    241      1.1  christos 	     "\[^\r\n\]+BuiltinDisassembler\\s+\\(Matches current architecture\\)" \
    242      1.1  christos 	     "GLOBAL\\s+BuiltinDisassembler"] \
    243      1.1  christos 	"list disassemblers, multiple disassemblers registered"
    244      1.1  christos 
    245      1.1  christos     # Check that disassembling main (with the BuiltinDisassembler in
    246      1.1  christos     # place) doesn't cause GDB to crash.  The hope is that
    247      1.1  christos     # disassembling main will result in a call to print_address, which
    248      1.1  christos     # is where the problem was.
    249      1.1  christos     gdb_test "disassemble main" ".*"
    250      1.1  christos }
    251      1.1  christos 
    252      1.1  christos # Check the attempt to create a "new" DisassembleInfo object fails.
    253      1.1  christos with_test_prefix "Bad DisassembleInfo creation" {
    254      1.1  christos     gdb_test_no_output "python my_info = InvalidDisassembleInfo()"
    255      1.1  christos     gdb_test "python print(my_info.is_valid())" "True"
    256      1.1  christos     gdb_test "python gdb.disassembler.builtin_disassemble(my_info)" \
    257      1.1  christos 	[multi_line \
    258  1.1.1.2  christos 	     "RuntimeError.*: DisassembleInfo is no longer valid\\." \
    259  1.1.1.2  christos 	     "Error occurred in Python.*"]
    260  1.1.1.2  christos }
    261  1.1.1.2  christos 
    262  1.1.1.2  christos # Some of the disassembler related types should not be sub-typed,
    263  1.1.1.2  christos # check these now.
    264  1.1.1.2  christos with_test_prefix "check inheritance" {
    265  1.1.1.2  christos     foreach_with_prefix type {gdb.disassembler.DisassemblerResult \
    266  1.1.1.2  christos 				  gdb.disassembler.DisassemblerPart
    267  1.1.1.2  christos 				  gdb.disassembler.DisassemblerTextPart \
    268  1.1.1.2  christos 				  gdb.disassembler.DisassemblerAddressPart} {
    269  1.1.1.2  christos 	set type_ptn [string_to_regexp $type]
    270  1.1.1.2  christos 	gdb_test_multiline "Sub-class a breakpoint" \
    271  1.1.1.2  christos 	    "python" "" \
    272  1.1.1.2  christos 	    "class InvalidResultType($type):" "" \
    273  1.1.1.2  christos 	    "   def __init__(self):" "" \
    274  1.1.1.2  christos 	    "     pass" "" \
    275  1.1.1.2  christos 	    "end" \
    276  1.1.1.2  christos 	    [multi_line \
    277  1.1.1.2  christos 		 "TypeError.*: type '${type_ptn}' is not an acceptable base type" \
    278  1.1.1.2  christos 		 "Error occurred in Python.*"]
    279  1.1.1.2  christos     }
    280  1.1.1.2  christos }
    281  1.1.1.2  christos 
    282  1.1.1.2  christos 
    283  1.1.1.2  christos # Test some error conditions when creating a DisassemblerResult object.
    284  1.1.1.2  christos gdb_test "python result = gdb.disassembler.DisassemblerResult()" \
    285  1.1.1.2  christos     [multi_line \
    286  1.1.1.2  christos 	 "TypeError.*: [missing_arg_pattern length 1]" \
    287  1.1.1.2  christos 	 "Error occurred in Python.*"] \
    288  1.1.1.2  christos     "try to create a DisassemblerResult without a length argument"
    289  1.1.1.2  christos 
    290  1.1.1.2  christos foreach len {0 -1} {
    291  1.1.1.2  christos     gdb_test "python result = gdb.disassembler.DisassemblerResult($len)" \
    292  1.1.1.2  christos 	[multi_line \
    293  1.1.1.2  christos 	     "ValueError.*: Length must be greater than 0\\." \
    294  1.1.1.2  christos 	     "Error occurred in Python.*"] \
    295  1.1.1.2  christos 	"try to create a DisassemblerResult with length $len"
    296  1.1.1.2  christos }
    297  1.1.1.2  christos 
    298  1.1.1.2  christos # Check we can't directly create DisassemblerTextPart or
    299  1.1.1.2  christos # DisassemblerAddressPart objects.
    300  1.1.1.2  christos foreach type {DisassemblerTextPart DisassemblerAddressPart} {
    301  1.1.1.2  christos     gdb_test "python result = gdb.disassembler.${type}()" \
    302  1.1.1.2  christos 	[multi_line \
    303  1.1.1.2  christos 	     "RuntimeError.*: Cannot create instances of DisassemblerPart\\." \
    304  1.1.1.2  christos 	     "Error occurred in Python.*"] \
    305  1.1.1.2  christos 	 "try to create an instance of ${type}"
    306      1.1  christos }
    307