From: Andrew Burgess <aburgess@redhat.com>
To: Simon Marchi <simark@simark.ca>, gdb-patches@sourceware.org
Subject: Re: [PATCHv2 1/3] gdb/python: avoid throwing an exception over libopcodes code
Date: Mon, 28 Nov 2022 19:26:01 +0000 [thread overview]
Message-ID: <87pmd62852.fsf@redhat.com> (raw)
In-Reply-To: <b4dd3a12-dfc2-8d18-f0b2-62dd2be3e10f@simark.ca>
Simon Marchi <simark@simark.ca> writes:
> On 10/27/22 11:38, Andrew Burgess wrote:
>> Bug gdb/29712 identifies a problem with the Python disassembler API.
>> In some cases GDB will try to throw an exception through the
>> libopcodes disassembler code, however, not all targets include
>> exception unwind information when compiling C code, for targets that
>> don't include this information GDB will terminate when trying to pass
>> the exception through libopcodes.
>>
>> To explain what GDB is trying to do, consider the following trivial
>> use of the Python disassembler API:
>>
>> class ExampleDisassembler(gdb.disassembler.Disassembler):
>>
>> class MyInfo(gdb.disassembler.DisassembleInfo):
>> def __init__(self, info):
>> super().__init__(info)
>>
>> def read_memory(self, length, offset):
>> return super().read_memory(length, offset)
>>
>> def __init__(self):
>> super().__init__("ExampleDisassembler")
>>
>> def __call__(self, info):
>> info = self.MyInfo(info)
>> return gdb.disassembler.builtin_disassemble(info)
>>
>> This disassembler doesn't add any value, it defers back to GDB to do
>> all the actual work, but it serves to allow us to discuss the problem.
>>
>> The problem occurs when a Python exception is raised by the
>> MyInfo.read_memory method. The MyInfo.read_memory method is called
>> from the C++ function gdbpy_disassembler::read_memory_func. The C++
>> stack at the point this function is called looks like this:
>>
>> #0 gdbpy_disassembler::read_memory_func (memaddr=4198805, buff=0x7fff9ab9d2a8 "\220ӹ\232\377\177", len=1, info=0x7fff9ab9d558) at ../../src/gdb/python/py-disasm.c:510
>> #1 0x000000000104ba06 in fetch_data (info=0x7fff9ab9d558, addr=0x7fff9ab9d2a9 "ӹ\232\377\177") at ../../src/opcodes/i386-dis.c:305
>> #2 0x000000000104badb in ckprefix (ins=0x7fff9ab9d100) at ../../src/opcodes/i386-dis.c:8571
>> #3 0x000000000104e28e in print_insn (pc=4198805, info=0x7fff9ab9d558, intel_syntax=-1) at ../../src/opcodes/i386-dis.c:9548
>> #4 0x000000000104f4d4 in print_insn_i386 (pc=4198805, info=0x7fff9ab9d558) at ../../src/opcodes/i386-dis.c:9949
>> #5 0x00000000004fa7ea in default_print_insn (memaddr=4198805, info=0x7fff9ab9d558) at ../../src/gdb/arch-utils.c:1033
>> #6 0x000000000094fe5e in i386_print_insn (pc=4198805, info=0x7fff9ab9d558) at ../../src/gdb/i386-tdep.c:4072
>> #7 0x0000000000503d49 in gdbarch_print_insn (gdbarch=0x5335560, vma=4198805, info=0x7fff9ab9d558) at ../../src/gdb/gdbarch.c:3351
>> #8 0x0000000000bcc8c6 in disasmpy_builtin_disassemble (self=0x7f2ab07f54d0, args=0x7f2ab0789790, kw=0x0) at ../../src/gdb/python/py-disasm.c:324
>>
>> ### ... snip lots of frames as we pass through Python itself ...
>>
>> #22 0x0000000000bcd860 in gdbpy_print_insn (gdbarch=0x5335560, memaddr=0x401195, info=0x7fff9ab9e3c8) at ../../src/gdb/python/py-disasm.c:783
>> #23 0x00000000008995a5 in ext_lang_print_insn (gdbarch=0x5335560, address=0x401195, info=0x7fff9ab9e3c8) at ../../src/gdb/extension.c:939
>> #24 0x0000000000741aaa in gdb_print_insn_1 (gdbarch=0x5335560, vma=0x401195, info=0x7fff9ab9e3c8) at ../../src/gdb/disasm.c:1078
>> #25 0x0000000000741bab in gdb_disassembler::print_insn (this=0x7fff9ab9e3c0, memaddr=0x401195, branch_delay_insns=0x0) at ../../src/gdb/disasm.c:1101
>>
>> So gdbpy_disassembler::read_memory_func is called from the libopcodes
>> disassembler to read memory, this C++ function then calls into user
>> supplied Python code to do the work.
>>
>> If the user supplied Python code raises an gdb.MemoryError exception
>> indicating the memory read failed, this this is fine. The C++ code
>
> "this this"
>
>> converts this exception back into a return value that libopcodes can
>> understand, and returns to libopcodes.
>>
>> However, if the user supplied Python code raises some other exception,
>> what we want is for this exception to propagate through GDB and appear
>> as if raised by the call to gdb.disassembler.builtin_disassemble. To
>> achieve this, when gdbpy_disassembler::read_memory_func spots an
>> unknown Python exception, we must pass the information about this
>> exception from frame #0 to frame #8 in the above backtrace. Frame #8
>> is the C++ implementation of gdb.disassembler.builtin_disassemble, and
>> so it is this function that we want to re-raise the unknown Python
>> exception, so the user can, if they want, catch the exception in their
>> code.
>>
>> The previous mechanism by which the exception was passed was to pack
>> the details of the Python exception into a C++ exception, then throw
>> the exception from frame #0, and catch the exception in frame #8,
>> unpack the details of the Python exception, and re-raise it.
>>
>> However, this relies on the exception passing through frames #1 to #7,
>> some of which are in libopcodes, which is C code, and so, might not be
>> compiled with exception support.
>>
>> This commit proposes an alternative solution that does not rely on
>> throwing a C++ exception.
>>
>> When we spot an unhandled Python exception in frame #0, we will store
>> the details of this exception within the gdbpy_disassembler object
>> currently in use. Then we return to libopcodes a value indicating
>> that the memory_read failed.
>>
>> libopcodes will now continue to disassemble as though that memory read
>> failed (with one special case described below), then, when we
>> eventually return to disasmpy_builtin_disassemble we check to see if
>> there is an exception stored in the gdbpy_disassembler object. If
>> there is then this exception can immediately be installed, and then we
>> return back to Python, when the user will be able to catch the
>> exception.
>>
>> There is one extra change in gdbpy_disassembler::read_memory_func.
>> After the first call that results in an exception being stored on the
>> gdbpy_disassembler object, any future calls to the ::read_memory_func
>> function will immediately return as if the read failed. This avoids
>> any additional calls into user supplied Python code.
>>
>> My thinking here is that should the first call fail with some unknown
>> error, GDB should not keep trying with any additional calls. This
>> maintains the illusion that the exception raised from
>> MyInfo.read_memory is immediately raised by
>> gdb.disassembler.builtin_disassemble. I have no tests for this change
>> though - to trigger this issue would rely on a libopcodes disassembler
>> that will try to read further memory even after the first failed
>> read. I'm not aware of any such disassembler that currently does
>> this, but that doesn't mean such a disassembler couldn't exist in the
>> future.
>
> After understanding this, I'm fine with the idea. You can add:
>
> Approved-By: Simon Marchi <simon.marchi@efficios.com>
Thanks. I pushed this series.
Andrew
next prev parent reply other threads:[~2022-11-28 19:26 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-10-24 12:50 [PATCH] " Andrew Burgess
2022-10-24 15:50 ` Simon Marchi
2022-10-24 17:22 ` Andrew Burgess
2022-10-24 17:45 ` [PATCH] gdb/disasm: mark functions passed to the disassembler noexcept Andrew Burgess
2022-10-24 18:24 ` Simon Marchi
2022-10-24 18:20 ` [PATCH] gdb/python: avoid throwing an exception over libopcodes code Simon Marchi
2022-10-27 10:38 ` Andrew Burgess
2022-10-27 15:38 ` [PATCHv2 0/3] " Andrew Burgess
2022-10-27 15:38 ` [PATCHv2 1/3] " Andrew Burgess
2022-11-28 14:39 ` Simon Marchi
2022-11-28 19:26 ` Andrew Burgess [this message]
2022-10-27 15:38 ` [PATCHv2 2/3] gdb/disasm: mark functions passed to the disassembler noexcept Andrew Burgess
2022-10-27 15:38 ` [PATCHv2 3/3] gdb: mark disassembler function callback types as noexcept Andrew Burgess
2022-11-18 16:57 ` [PATCHv3 0/3] gdb/python: avoid throwing an exception over libopcodes code Andrew Burgess
2022-11-18 16:57 ` [PATCHv3 1/3] " Andrew Burgess
2022-11-18 16:57 ` [PATCHv3 2/3] gdb/disasm: mark functions passed to the disassembler noexcept Andrew Burgess
2022-11-18 16:57 ` [PATCHv3 3/3] gdb: mark disassembler function callback types as noexcept Andrew Burgess
2022-11-28 8:35 ` [PATCHv3 0/3] gdb/python: avoid throwing an exception over libopcodes code Tom de Vries
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87pmd62852.fsf@redhat.com \
--to=aburgess@redhat.com \
--cc=gdb-patches@sourceware.org \
--cc=simark@simark.ca \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).