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