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