| # Copyright (C) 2021-2022 Free Software Foundation, Inc. | |
| # This program is free software; you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation; either version 3 of the License, or | |
| # (at your option) any later version. | |
| # | |
| # This program is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| # This file is part of the GDB testsuite. It validates the Python | |
| # disassembler API. | |
| load_lib gdb-python.exp | |
| standard_testfile | |
| if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} "debug"] } { | |
| return -1 | |
| } | |
| # Skip all tests if Python scripting is not enabled. | |
| if { [skip_python_tests] } { continue } | |
| if ![runto_main] then { | |
| fail "can't run to main" | |
| return 0 | |
| } | |
| set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] | |
| gdb_test "source ${pyfile}" "Python script imported" \ | |
| "import python scripts" | |
| gdb_breakpoint [gdb_get_line_number "Break here."] | |
| gdb_continue_to_breakpoint "Break here." | |
| set curr_pc [get_valueof "/x" "\$pc" "*unknown*"] | |
| gdb_test_no_output "python current_pc = ${curr_pc}" | |
| # The current pc will be something like 0x1234 with no leading zeros. | |
| # However, in the disassembler output addresses are padded with zeros. | |
| # This substitution changes 0x1234 to 0x0*1234, which can then be used | |
| # as a regexp in the disassembler output matching. | |
| set curr_pc_pattern [string replace ${curr_pc} 0 1 "0x0*"] | |
| # Grab the name of the current architecture, this is used in the tests | |
| # patterns below. | |
| set curr_arch [get_python_valueof "gdb.selected_inferior().architecture().name()" "*unknown*"] | |
| # Helper proc that removes all registered disassemblers. | |
| proc py_remove_all_disassemblers {} { | |
| gdb_test_no_output "python remove_all_python_disassemblers()" | |
| } | |
| # A list of test plans. Each plan is a list of two elements, the | |
| # first element is the name of a class in py-disasm.py, this is a | |
| # disassembler class. The second element is a pattern that should be | |
| # matched in the disassembler output. | |
| # | |
| # Each different disassembler tests some different feature of the | |
| # Python disassembler API. | |
| set unknown_error_pattern "unknown disassembler error \\(error = -1\\)" | |
| set addr_pattern "\r\n=> ${curr_pc_pattern} <\[^>\]+>:\\s+" | |
| set base_pattern "${addr_pattern}nop" | |
| set test_plans \ | |
| [list \ | |
| [list "" "${base_pattern}\r\n.*"] \ | |
| [list "GlobalNullDisassembler" "${base_pattern}\r\n.*"] \ | |
| [list "GlobalPreInfoDisassembler" "${base_pattern}\\s+## ad = $hex, ar = ${curr_arch}\r\n.*"] \ | |
| [list "GlobalPostInfoDisassembler" "${base_pattern}\\s+## ad = $hex, ar = ${curr_arch}\r\n.*"] \ | |
| [list "GlobalReadDisassembler" "${base_pattern}\\s+## bytes =( $hex)+\r\n.*"] \ | |
| [list "GlobalAddrDisassembler" "${base_pattern}\\s+## addr = ${curr_pc_pattern} <\[^>\]+>\r\n.*"] \ | |
| [list "GdbErrorEarlyDisassembler" "${addr_pattern}GdbError instead of a result\r\n${unknown_error_pattern}"] \ | |
| [list "RuntimeErrorEarlyDisassembler" "${addr_pattern}Python Exception <class 'RuntimeError'>: RuntimeError instead of a result\r\n\r\n${unknown_error_pattern}"] \ | |
| [list "GdbErrorLateDisassembler" "${addr_pattern}GdbError after builtin disassembler\r\n${unknown_error_pattern}"] \ | |
| [list "RuntimeErrorLateDisassembler" "${addr_pattern}Python Exception <class 'RuntimeError'>: RuntimeError after builtin disassembler\r\n\r\n${unknown_error_pattern}"] \ | |
| [list "MemoryErrorEarlyDisassembler" "${base_pattern}\\s+## AFTER ERROR\r\n.*"] \ | |
| [list "MemoryErrorLateDisassembler" "${addr_pattern}Cannot access memory at address ${curr_pc_pattern}"] \ | |
| [list "RethrowMemoryErrorDisassembler" "${addr_pattern}Cannot access memory at address $hex"] \ | |
| [list "ReadMemoryMemoryErrorDisassembler" "${addr_pattern}Cannot access memory at address ${curr_pc_pattern}"] \ | |
| [list "ReadMemoryGdbErrorDisassembler" "${addr_pattern}read_memory raised GdbError\r\n${unknown_error_pattern}"] \ | |
| [list "ReadMemoryRuntimeErrorDisassembler" "${addr_pattern}Python Exception <class 'RuntimeError'>: read_memory raised RuntimeError\r\n\r\n${unknown_error_pattern}"] \ | |
| [list "ReadMemoryCaughtMemoryErrorDisassembler" "${addr_pattern}nop\r\n.*"] \ | |
| [list "ReadMemoryCaughtGdbErrorDisassembler" "${addr_pattern}nop\r\n.*"] \ | |
| [list "ReadMemoryCaughtRuntimeErrorDisassembler" "${addr_pattern}nop\r\n.*"] \ | |
| [list "MemorySourceNotABufferDisassembler" "${addr_pattern}Python Exception <class 'TypeError'>: Result from read_memory is not a buffer\r\n\r\n${unknown_error_pattern}"] \ | |
| [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}"] \ | |
| [list "ResultOfWrongType" "${addr_pattern}Python Exception <class 'TypeError'>: Result is not a DisassemblerResult.\r\n.*"] \ | |
| [list "ResultWithInvalidLength" "${addr_pattern}Python Exception <class 'ValueError'>: Invalid length attribute: length must be greater than 0.\r\n.*"] \ | |
| [list "ResultWithInvalidString" "${addr_pattern}Python Exception <class 'ValueError'>: String attribute must not be empty.\r\n.*"]] | |
| # Now execute each test plan. | |
| foreach plan $test_plans { | |
| set global_disassembler_name [lindex $plan 0] | |
| set expected_pattern [lindex $plan 1] | |
| with_test_prefix "global_disassembler=${global_disassembler_name}" { | |
| # Remove all existing disassemblers. | |
| py_remove_all_disassemblers | |
| # If we have a disassembler to load, do it now. | |
| if { $global_disassembler_name != "" } { | |
| gdb_test_no_output "python add_global_disassembler($global_disassembler_name)" | |
| } | |
| # Disassemble test, and check the disassembler output. | |
| gdb_test "disassemble test" $expected_pattern | |
| } | |
| } | |
| # Check some errors relating to DisassemblerResult creation. | |
| with_test_prefix "DisassemblerResult errors" { | |
| gdb_test "python gdb.disassembler.DisassemblerResult(0, 'abc')" \ | |
| [multi_line \ | |
| "ValueError: Length must be greater than 0." \ | |
| "Error while executing Python code."] | |
| gdb_test "python gdb.disassembler.DisassemblerResult(-1, 'abc')" \ | |
| [multi_line \ | |
| "ValueError: Length must be greater than 0." \ | |
| "Error while executing Python code."] | |
| gdb_test "python gdb.disassembler.DisassemblerResult(1, '')" \ | |
| [multi_line \ | |
| "ValueError: String must not be empty." \ | |
| "Error while executing Python code."] | |
| } | |
| # Check that the architecture specific disassemblers can override the | |
| # global disassembler. | |
| # | |
| # First, register a global disassembler, and check it is in place. | |
| with_test_prefix "GLOBAL tagging disassembler" { | |
| py_remove_all_disassemblers | |
| gdb_test_no_output "python gdb.disassembler.register_disassembler(TaggingDisassembler(\"GLOBAL\"), None)" | |
| gdb_test "disassemble test" "${base_pattern}\\s+## tag = GLOBAL\r\n.*" | |
| } | |
| # Now register an architecture specific disassembler, and check it | |
| # overrides the global disassembler. | |
| with_test_prefix "LOCAL tagging disassembler" { | |
| gdb_test_no_output "python gdb.disassembler.register_disassembler(TaggingDisassembler(\"LOCAL\"), \"${curr_arch}\")" | |
| gdb_test "disassemble test" "${base_pattern}\\s+## tag = LOCAL\r\n.*" | |
| } | |
| # Now remove the architecture specific disassembler, and check that | |
| # the global disassembler kicks back in. | |
| with_test_prefix "GLOBAL tagging disassembler again" { | |
| gdb_test_no_output "python gdb.disassembler.register_disassembler(None, \"${curr_arch}\")" | |
| gdb_test "disassemble test" "${base_pattern}\\s+## tag = GLOBAL\r\n.*" | |
| } | |
| # Check that a DisassembleInfo becomes invalid after the call into the | |
| # disassembler. | |
| with_test_prefix "DisassembleInfo becomes invalid" { | |
| py_remove_all_disassemblers | |
| gdb_test_no_output "python add_global_disassembler(GlobalCachingDisassembler)" | |
| gdb_test "disassemble test" "${base_pattern}\\s+## CACHED\r\n.*" | |
| gdb_test "python GlobalCachingDisassembler.check()" "PASS" | |
| } | |
| # Test the memory source aspect of the builtin disassembler. | |
| with_test_prefix "memory source api" { | |
| py_remove_all_disassemblers | |
| gdb_test_no_output "python analyzing_disassembler = add_global_disassembler(AnalyzingDisassembler)" | |
| gdb_test "disassemble test" "${base_pattern}\r\n.*" | |
| gdb_test "python analyzing_disassembler.find_replacement_candidate()" \ | |
| "Replace from $hex to $hex with NOP" | |
| gdb_test "disassemble test" "${base_pattern}\r\n.*" \ | |
| "second disassembler pass" | |
| gdb_test "python analyzing_disassembler.check()" \ | |
| "PASS" | |
| } | |
| # Test the 'maint info python-disassemblers command. | |
| with_test_prefix "maint info python-disassemblers" { | |
| py_remove_all_disassemblers | |
| gdb_test "maint info python-disassemblers" "No Python disassemblers registered\\." \ | |
| "list disassemblers, none registered" | |
| gdb_test_no_output "python disasm = add_global_disassembler(BuiltinDisassembler)" | |
| gdb_test "maint info python-disassemblers" \ | |
| [multi_line \ | |
| "Architecture\\s+Disassember Name" \ | |
| "GLOBAL\\s+BuiltinDisassembler\\s+\\(Matches current architecture\\)"] \ | |
| "list disassemblers, single global disassembler" | |
| gdb_test_no_output "python arch = gdb.selected_inferior().architecture().name()" | |
| gdb_test_no_output "python gdb.disassembler.register_disassembler(disasm, arch)" | |
| gdb_test "maint info python-disassemblers" \ | |
| [multi_line \ | |
| "Architecture\\s+Disassember Name" \ | |
| "\[^\r\n\]+BuiltinDisassembler\\s+\\(Matches current architecture\\)" \ | |
| "GLOBAL\\s+BuiltinDisassembler"] \ | |
| "list disassemblers, multiple disassemblers registered" | |
| # Check that disassembling main (with the BuiltinDisassembler in | |
| # place) doesn't cause GDB to crash. The hope is that | |
| # disassembling main will result in a call to print_address, which | |
| # is where the problem was. | |
| gdb_test "disassemble main" ".*" | |
| } | |
| # Check the attempt to create a "new" DisassembleInfo object fails. | |
| with_test_prefix "Bad DisassembleInfo creation" { | |
| gdb_test_no_output "python my_info = InvalidDisassembleInfo()" | |
| gdb_test "python print(my_info.is_valid())" "True" | |
| gdb_test "python gdb.disassembler.builtin_disassemble(my_info)" \ | |
| [multi_line \ | |
| "RuntimeError: DisassembleInfo is no longer valid\\." \ | |
| "Error while executing Python code\\."] | |
| } |