1# Copyright (C) 2021-2023 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 19load_lib gdb-python.exp 20 21standard_testfile 22 23if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} "debug"] } { 24 return -1 25} 26 27# Skip all tests if Python scripting is not enabled. 28if { [skip_python_tests] } { continue } 29 30if {![runto_main]} { 31 fail "can't run to main" 32 return 0 33} 34 35set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] 36 37gdb_test "source ${pyfile}" "Python script imported" \ 38 "import python scripts" 39 40gdb_breakpoint [gdb_get_line_number "Break here."] 41gdb_continue_to_breakpoint "Break here." 42 43set curr_pc [get_valueof "/x" "\$pc" "*unknown*"] 44 45gdb_test_no_output "python current_pc = ${curr_pc}" 46 47# The current pc will be something like 0x1234 with no leading zeros. 48# However, in the disassembler output addresses are padded with zeros. 49# This substitution changes 0x1234 to 0x0*1234, which can then be used 50# as a regexp in the disassembler output matching. 51set curr_pc_pattern [string replace ${curr_pc} 0 1 "0x0*"] 52 53# Grab the name of the current architecture, this is used in the tests 54# patterns below. 55set curr_arch [get_python_valueof "gdb.selected_inferior().architecture().name()" "*unknown*"] 56 57# Helper proc that removes all registered disassemblers. 58proc py_remove_all_disassemblers {} { 59 gdb_test_no_output "python remove_all_python_disassemblers()" 60} 61 62# A list of test plans. Each plan is a list of two elements, the 63# first element is the name of a class in py-disasm.py, this is a 64# disassembler class. The second element is a pattern that should be 65# matched in the disassembler output. 66# 67# Each different disassembler tests some different feature of the 68# Python disassembler API. 69set nop "(nop|nop\t0)" 70set unknown_error_pattern "unknown disassembler error \\(error = -1\\)" 71set addr_pattern "\r\n=> ${curr_pc_pattern} <\[^>\]+>:\\s+" 72set base_pattern "${addr_pattern}${nop}" 73set test_plans \ 74 [list \ 75 [list "" "${base_pattern}\r\n.*"] \ 76 [list "GlobalNullDisassembler" "${base_pattern}\r\n.*"] \ 77 [list "GlobalPreInfoDisassembler" "${base_pattern}\\s+## ad = $hex, ar = ${curr_arch}\r\n.*"] \ 78 [list "GlobalPostInfoDisassembler" "${base_pattern}\\s+## ad = $hex, ar = ${curr_arch}\r\n.*"] \ 79 [list "GlobalReadDisassembler" "${base_pattern}\\s+## bytes =( $hex)+\r\n.*"] \ 80 [list "GlobalAddrDisassembler" "${base_pattern}\\s+## addr = ${curr_pc_pattern} <\[^>\]+>\r\n.*"] \ 81 [list "GdbErrorEarlyDisassembler" "${addr_pattern}GdbError instead of a result\r\n${unknown_error_pattern}"] \ 82 [list "RuntimeErrorEarlyDisassembler" "${addr_pattern}Python Exception <class 'RuntimeError'>: RuntimeError instead of a result\r\n\r\n${unknown_error_pattern}"] \ 83 [list "GdbErrorLateDisassembler" "${addr_pattern}GdbError after builtin disassembler\r\n${unknown_error_pattern}"] \ 84 [list "RuntimeErrorLateDisassembler" "${addr_pattern}Python Exception <class 'RuntimeError'>: RuntimeError after builtin disassembler\r\n\r\n${unknown_error_pattern}"] \ 85 [list "MemoryErrorEarlyDisassembler" "${base_pattern}\\s+## AFTER ERROR\r\n.*"] \ 86 [list "MemoryErrorLateDisassembler" "${addr_pattern}Cannot access memory at address ${curr_pc_pattern}"] \ 87 [list "RethrowMemoryErrorDisassembler" "${addr_pattern}Cannot access memory at address $hex"] \ 88 [list "ReadMemoryMemoryErrorDisassembler" "${addr_pattern}Cannot access memory at address ${curr_pc_pattern}"] \ 89 [list "ReadMemoryGdbErrorDisassembler" "${addr_pattern}read_memory raised GdbError\r\n${unknown_error_pattern}"] \ 90 [list "ReadMemoryRuntimeErrorDisassembler" "${addr_pattern}Python Exception <class 'RuntimeError'>: read_memory raised RuntimeError\r\n\r\n${unknown_error_pattern}"] \ 91 [list "ReadMemoryCaughtMemoryErrorDisassembler" "${addr_pattern}${nop}\r\n.*"] \ 92 [list "ReadMemoryCaughtGdbErrorDisassembler" "${addr_pattern}${nop}\r\n.*"] \ 93 [list "ReadMemoryCaughtRuntimeErrorDisassembler" "${addr_pattern}${nop}\r\n.*"] \ 94 [list "MemorySourceNotABufferDisassembler" "${addr_pattern}Python Exception <class 'TypeError'>: Result from read_memory is not a buffer\r\n\r\n${unknown_error_pattern}"] \ 95 [list "MemorySourceBufferTooLongDisassembler" "${addr_pattern}Python Exception <class 'ValueError'>: Buffer returned from read_memory is sized $decimal instead of the expected $decimal\r\n\r\n${unknown_error_pattern}"] \ 96 [list "ResultOfWrongType" "${addr_pattern}Python Exception <class 'TypeError'>: Result is not a DisassemblerResult.\r\n.*"] \ 97 [list "ResultWithInvalidLength" "${addr_pattern}Python Exception <class 'ValueError'>: Invalid length attribute: length must be greater than 0.\r\n.*"] \ 98 [list "ResultWithInvalidString" "${addr_pattern}Python Exception <class 'ValueError'>: String attribute must not be empty.\r\n.*"]] 99 100# Now execute each test plan. 101foreach plan $test_plans { 102 set global_disassembler_name [lindex $plan 0] 103 set expected_pattern [lindex $plan 1] 104 105 with_test_prefix "global_disassembler=${global_disassembler_name}" { 106 # Remove all existing disassemblers. 107 py_remove_all_disassemblers 108 109 # If we have a disassembler to load, do it now. 110 if { $global_disassembler_name != "" } { 111 gdb_test_no_output "python add_global_disassembler($global_disassembler_name)" 112 } 113 114 # Disassemble test, and check the disassembler output. 115 gdb_test "disassemble test" $expected_pattern 116 } 117} 118 119# Check some errors relating to DisassemblerResult creation. 120with_test_prefix "DisassemblerResult errors" { 121 gdb_test "python gdb.disassembler.DisassemblerResult(0, 'abc')" \ 122 [multi_line \ 123 "ValueError: Length must be greater than 0." \ 124 "Error while executing Python code."] 125 gdb_test "python gdb.disassembler.DisassemblerResult(-1, 'abc')" \ 126 [multi_line \ 127 "ValueError: Length must be greater than 0." \ 128 "Error while executing Python code."] 129 gdb_test "python gdb.disassembler.DisassemblerResult(1, '')" \ 130 [multi_line \ 131 "ValueError: String must not be empty." \ 132 "Error while executing Python code."] 133} 134 135# Check that the architecture specific disassemblers can override the 136# global disassembler. 137# 138# First, register a global disassembler, and check it is in place. 139with_test_prefix "GLOBAL tagging disassembler" { 140 py_remove_all_disassemblers 141 gdb_test_no_output "python gdb.disassembler.register_disassembler(TaggingDisassembler(\"GLOBAL\"), None)" 142 gdb_test "disassemble test" "${base_pattern}\\s+## tag = GLOBAL\r\n.*" 143} 144 145# Now register an architecture specific disassembler, and check it 146# overrides the global disassembler. 147with_test_prefix "LOCAL tagging disassembler" { 148 gdb_test_no_output "python gdb.disassembler.register_disassembler(TaggingDisassembler(\"LOCAL\"), \"${curr_arch}\")" 149 gdb_test "disassemble test" "${base_pattern}\\s+## tag = LOCAL\r\n.*" 150} 151 152# Now remove the architecture specific disassembler, and check that 153# the global disassembler kicks back in. 154with_test_prefix "GLOBAL tagging disassembler again" { 155 gdb_test_no_output "python gdb.disassembler.register_disassembler(None, \"${curr_arch}\")" 156 gdb_test "disassemble test" "${base_pattern}\\s+## tag = GLOBAL\r\n.*" 157} 158 159# Check that a DisassembleInfo becomes invalid after the call into the 160# disassembler. 161with_test_prefix "DisassembleInfo becomes invalid" { 162 py_remove_all_disassemblers 163 gdb_test_no_output "python add_global_disassembler(GlobalCachingDisassembler)" 164 gdb_test "disassemble test" "${base_pattern}\\s+## CACHED\r\n.*" 165 gdb_test "python GlobalCachingDisassembler.check()" "PASS" 166} 167 168# Test the memory source aspect of the builtin disassembler. 169with_test_prefix "memory source api" { 170 py_remove_all_disassemblers 171 gdb_test_no_output "python analyzing_disassembler = add_global_disassembler(AnalyzingDisassembler)" 172 gdb_test "disassemble test" "${base_pattern}\r\n.*" 173 gdb_test "python analyzing_disassembler.find_replacement_candidate()" \ 174 "Replace from $hex to $hex with NOP" 175 gdb_test "disassemble test" "${base_pattern}\r\n.*" \ 176 "second disassembler pass" 177 gdb_test "python analyzing_disassembler.check()" \ 178 "PASS" 179} 180 181# Test the 'maint info python-disassemblers command. 182with_test_prefix "maint info python-disassemblers" { 183 py_remove_all_disassemblers 184 gdb_test "maint info python-disassemblers" "No Python disassemblers registered\\." \ 185 "list disassemblers, none registered" 186 gdb_test_no_output "python disasm = add_global_disassembler(BuiltinDisassembler)" 187 gdb_test "maint info python-disassemblers" \ 188 [multi_line \ 189 "Architecture\\s+Disassember Name" \ 190 "GLOBAL\\s+BuiltinDisassembler\\s+\\(Matches current architecture\\)"] \ 191 "list disassemblers, single global disassembler" 192 gdb_test_no_output "python arch = gdb.selected_inferior().architecture().name()" 193 gdb_test_no_output "python gdb.disassembler.register_disassembler(disasm, arch)" 194 gdb_test "maint info python-disassemblers" \ 195 [multi_line \ 196 "Architecture\\s+Disassember Name" \ 197 "\[^\r\n\]+BuiltinDisassembler\\s+\\(Matches current architecture\\)" \ 198 "GLOBAL\\s+BuiltinDisassembler"] \ 199 "list disassemblers, multiple disassemblers registered" 200 201 # Check that disassembling main (with the BuiltinDisassembler in 202 # place) doesn't cause GDB to crash. The hope is that 203 # disassembling main will result in a call to print_address, which 204 # is where the problem was. 205 gdb_test "disassemble main" ".*" 206} 207 208# Check the attempt to create a "new" DisassembleInfo object fails. 209with_test_prefix "Bad DisassembleInfo creation" { 210 gdb_test_no_output "python my_info = InvalidDisassembleInfo()" 211 gdb_test "python print(my_info.is_valid())" "True" 212 gdb_test "python gdb.disassembler.builtin_disassemble(my_info)" \ 213 [multi_line \ 214 "RuntimeError: DisassembleInfo is no longer valid\\." \ 215 "Error while executing Python code\\."] 216} 217