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