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