binutils-gdb/gdb/testsuite/gdb.python/py-disasm.exp
Andrew Burgess 23aa2befce gdb/python: fix invalid use disassemble_info::stream
After this commit:

  commit 81384924cd
  Date:   Tue Apr 5 11:06:16 2022 +0100

      gdb: have gdb_disassemble_info carry 'this' in its stream pointer

The disassemble_info::stream field will no longer be a ui_file*.  That
commit failed to update one location in py-disasm.c though.

While running some tests using the Python disassembler API, I
triggered a call to gdbpy_disassembler::print_address_func, and, as I
had compiled GDB with the undefined behaviour sanitizer, GDB crashed
as the code currently (incorrectly) casts the stream field to be a
ui_file*.

In this commit I fix this error.

In order to test this case I had to tweak the existing test case a
little.  I also spotted some debug printf statements in py-disasm.py,
which I have removed.
2022-07-25 19:26:24 +01:00

216 lines
10 KiB
Plaintext

# 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\\."]
}