Summary: | Python pretty printer causes stack overflow when printing frame arguments | ||
---|---|---|---|
Product: | gdb | Reporter: | Jonathan Wakely <jwakely.gcc> |
Component: | python | Assignee: | Not yet assigned to anyone <unassigned> |
Status: | RESOLVED FIXED | ||
Severity: | normal | CC: | andrey.ayupov, fweimer, guinevere, simark, simon.marchi, ssbssa, vmiklos |
Priority: | P2 | ||
Version: | HEAD | ||
Target Milestone: | --- | ||
See Also: |
https://sourceware.org/bugzilla/show_bug.cgi?id=24393 https://bugzilla.redhat.com/show_bug.cgi?id=2046276 |
||
Host: | Target: | ||
Build: | Last reconfirmed: | ||
Attachments: | Test case |
Description
Jonathan Wakely
2022-02-03 11:45:24 UTC
On Windows I get a use-after-free of a frame_info pointer.
It happens because the target function call gdb.parse_and_eval('$__cat->name()') leads to reinit_frame_cache(), and print_frame_info() continues on with that now stale 'frame' pointer.
Full stack traces:
> unhandled exception code: 0xC0000005 (ACCESS_VIOLATION)
> exception on: '1 [4648]'
> 0x000000013F350000 C:\gdb\build64\gdb-git-python\gdb\gdb.exe
> 0x000000013F4A5687 C:\src\repos\binutils-gdb.git\gdb\frame.c:2545:3 [get_frame_pc_if_available(frame_info*, unsigned long long*)]
> 0x000000013F5F7E57 C:\src\repos\binutils-gdb.git\gdb\stack.c:1201:37 [print_frame_info(frame_print_options const&, frame_info*, int, print_what, int, int)]
> 0x000000013F5F887F C:\src\repos\binutils-gdb.git\gdb\stack.c:366:24 [print_stack_frame(frame_info*, int, print_what, int)]
> 0x000000013F4F5A97 C:\src\repos\binutils-gdb.git\gdb\infrun.c:8420:23 [print_stop_location]
> C:\src\repos\binutils-gdb.git\gdb\infrun.c:8436:25 [print_stop_event(ui_out*, bool)]
> 0x000000013F6630BD C:\src\repos\binutils-gdb.git\gdb\tui\tui-interp.c:99:19 [tui_on_normal_stop]
> 0x000000013F4F6F4F c:\msys64\mingw64\x86_64-w64-mingw32\include\c++\11.2.0\bits\std_function.h:560:9 [std::function<void (bpstats*, int)>::operator()(bpstats*, int) const]
> c:\src\repos\binutils-gdb.git\gdbsupport\observable.h:150:9 [gdb::observers::observable<bpstats*, int>::notify(bpstats*, int) const]
> C:\src\repos\binutils-gdb.git\gdb\infrun.c:8705:40 [normal_stop()]
> 0x000000013F502B42 C:\src\repos\binutils-gdb.git\gdb\infrun.c:4157:29 [fetch_inferior_event()]
> 0x000000013F3885FA C:\src\repos\binutils-gdb.git\gdb\async-event.c:335:31 [check_async_event_handlers()]
> 0x000000013F795901 C:\src\repos\binutils-gdb.git\gdbsupport\event-loop.cc:216:37 [gdb_do_one_event()]
> 0x000000013F64CD5B C:\src\repos\binutils-gdb.git\gdb\top.c:529:27 [wait_sync_command_done()]
> 0x000000013F64D650 C:\src\repos\binutils-gdb.git\gdb\top.c:546:28 [maybe_wait_sync_command_done(int)]
> C:\src\repos\binutils-gdb.git\gdb\top.c:687:36 [execute_command(char const*, int)]
> 0x000000013F5213BB C:\src\repos\binutils-gdb.git\gdb\main.c:523:15 [catch_command_errors]
> 0x000000013F5214E5 C:\src\repos\binutils-gdb.git\gdb\main.c:618:30 [execute_cmdargs]
> 0x000000013F524693 C:\src\repos\binutils-gdb.git\gdb\main.c:1322:19 [captured_main_1]
> 0x000000013F5250FC C:\src\repos\binutils-gdb.git\gdb\main.c:1343:19 [captured_main]
> C:\src\repos\binutils-gdb.git\gdb\main.c:1368:21 [gdb_main(captured_main_args*)]
> 0x000000013FA3C146 C:\src\repos\binutils-gdb.git\gdb\gdb.c:32:19 [main]
> 0x000000013F351430 C:\gcc\src\mingw-w64-v8.0.2\mingw-w64-crt\crt\crtexe.c:345:15 [__tmainCRTStartup]
> 0x000000013F3515B5 C:\gcc\src\mingw-w64-v8.0.2\mingw-w64-crt\crt\crtexe.c:220:9 [mainCRTStartup]
> read access violation at 0x000000039C0B0190
> freed block 0x000000039C0B0020 (size 4064, offset +368)
> allocated on: (#180516) '1 [4648]'
> [malloc]
> 0x000000013F350000 C:\gdb\build64\gdb-git-python\gdb\gdb.exe
> 0x000000013F37AEFB C:\src\repos\binutils-gdb.git\gdb\alloc.c:60:16 [xmalloc]
> 0x000000013F7B8E34 C:\src\repos\binutils-gdb.git\libiberty\obstack.c:94:12 [call_chunkfun]
> C:\src\repos\binutils-gdb.git\libiberty\obstack.c:141:37 [_obstack_begin_worker]
> 0x000000013F4A504B C:\src\repos\binutils-gdb.git\gdb\frame.c:2000:3 [reinit_frame_cache()]
> 0x000000013F4FF614 C:\src\repos\binutils-gdb.git\gdb\infrun.c:6021:18 [handle_signal_stop]
> 0x000000013F5012A0 C:\src\repos\binutils-gdb.git\gdb\infrun.c:4500:26 [handle_stop_requested]
> C:\src\repos\binutils-gdb.git\gdb\infrun.c:4494:1 [handle_stop_requested]
> C:\src\repos\binutils-gdb.git\gdb\infrun.c:5765:33 [handle_inferior_event]
> 0x000000013F502A44 C:\src\repos\binutils-gdb.git\gdb\infrun.c:4121:27 [fetch_inferior_event()]
> 0x000000013F3885FA C:\src\repos\binutils-gdb.git\gdb\async-event.c:335:31 [check_async_event_handlers()]
> 0x000000013F795901 C:\src\repos\binutils-gdb.git\gdbsupport\event-loop.cc:216:37 [gdb_do_one_event()]
> 0x000000013F64CD5B C:\src\repos\binutils-gdb.git\gdb\top.c:529:27 [wait_sync_command_done()]
> 0x000000013F64D650 C:\src\repos\binutils-gdb.git\gdb\top.c:546:28 [maybe_wait_sync_command_done(int)]
> C:\src\repos\binutils-gdb.git\gdb\top.c:687:36 [execute_command(char const*, int)]
> 0x000000013F5213BB C:\src\repos\binutils-gdb.git\gdb\main.c:523:15 [catch_command_errors]
> 0x000000013F5214E5 C:\src\repos\binutils-gdb.git\gdb\main.c:618:30 [execute_cmdargs]
> 0x000000013F524693 C:\src\repos\binutils-gdb.git\gdb\main.c:1322:19 [captured_main_1]
> 0x000000013F5250FC C:\src\repos\binutils-gdb.git\gdb\main.c:1343:19 [captured_main]
> C:\src\repos\binutils-gdb.git\gdb\main.c:1368:21 [gdb_main(captured_main_args*)]
> 0x000000013FA3C146 C:\src\repos\binutils-gdb.git\gdb\gdb.c:32:19 [main]
> 0x000000013F351430 C:\gcc\src\mingw-w64-v8.0.2\mingw-w64-crt\crt\crtexe.c:345:15 [__tmainCRTStartup]
> 0x000000013F3515B5 C:\gcc\src\mingw-w64-v8.0.2\mingw-w64-crt\crt\crtexe.c:220:9 [mainCRTStartup]
> freed on: '1 [4648]'
> [free]
> 0x000000013F350000 C:\gdb\build64\gdb-git-python\gdb\gdb.exe
> 0x000000013F7B9051 C:\src\repos\binutils-gdb.git\libiberty\obstack.c:103:5 [call_freefun]
> C:\src\repos\binutils-gdb.git\libiberty\obstack.c:280:7 [_obstack_free]
> 0x000000013F4A502B C:\src\repos\binutils-gdb.git\gdb\frame.c:1999:3 [reinit_frame_cache()]
> 0x000000013F4FAE4A C:\src\repos\binutils-gdb.git\gdb\infrun.c:3130:25 [proceed(unsigned long long, gdb_signal)]
> 0x000000013F4E4C9B C:\src\repos\binutils-gdb.git\gdb\infcall.c:611:15 [run_inferior_call]
> C:\src\repos\binutils-gdb.git\gdb\infcall.c:1277:27 [call_function_by_hand_dummy(value*, type*, gdb::array_view<value*>, void (*)(void*, int), void*)]
> 0x000000013F4E606A C:\src\repos\binutils-gdb.git\gdb\infcall.c:742:38 [call_function_by_hand(value*, type*, gdb::array_view<value*>)]
> 0x000000013F489AE9 C:\src\repos\binutils-gdb.git\gdb\eval.c:674:36 [evaluate_subexp_do_call(expression*, noside, value*, gdb::array_view<value*>, char const*, type*)]
> 0x000000013F48CEFA C:\src\repos\binutils-gdb.git\gdb\eval.c:966:34 [expr::structop_base_operation::evaluate_funcall(type*, expression*, noside, std::vector<std::unique_ptr<expr::operation, std::default_delete<expr::operation> >, std::allocator<std::unique_ptr<expr::operation, std::default_delete<expr::operation> > > > const&)]
> 0x000000013F920A29 C:\src\repos\binutils-gdb.git\gdb\expop.h:2178:54 [expr::funcall_operation::evaluate(type*, expression*, noside)]
> 0x000000013F48891B C:\src\repos\binutils-gdb.git\gdb\eval.c:101:39 [expression::evaluate(type*, noside)]
> 0x000000013F488BB9 C:\src\repos\binutils-gdb.git\gdb\eval.c:115:24 [evaluate_expression(expression*, type*)]
> C:\src\repos\binutils-gdb.git\gdb\eval.c:74:30 [parse_and_eval(char const*)]
> 0x000000013F59B899 C:\src\repos\binutils-gdb.git\gdb\python\python.c:945:31 [gdbpy_parse_and_eval]
> 0x0000000069E90000 c:\gdb\gdb-libs64\Python27\python27.dll
> 0x0000000069F88EA8 [PyCFunction_Call]
> 0x0000000069FEDFA7 [PyEval_GetFuncDesc]
> 0x0000000069FEB49A [PyEval_EvalFrameEx]
> 0x0000000069FEE177 [PyEval_GetFuncDesc]
> 0x0000000069FEE02E [PyEval_GetFuncDesc]
> 0x0000000069FEB49A [PyEval_EvalFrameEx]
> 0x0000000069FECA10 [PyEval_EvalCodeEx]
> 0x0000000069F77037 [PyFunction_SetClosure]
> 0x0000000069F42D22 [PyObject_Call]
> 0x0000000069F575D8 [PyMethod_New]
> 0x0000000069F42D22 [PyObject_Call]
> 0x0000000069F4359B [PyObject_CallMethodObjArgs]
> 0x000000013F350000 C:\gdb\build64\gdb-git-python\gdb\gdb.exe
> 0x000000013F589B6E C:\src\repos\binutils-gdb.git\gdb\python\py-prettyprint.c:200:17 [pretty_print_one_value]
> 0x000000013F589CB5 C:\src\repos\binutils-gdb.git\gdb\python\py-prettyprint.c:286:69 [print_string_repr]
> C:\src\repos\binutils-gdb.git\gdb\python\py-prettyprint.c:636:36 [gdbpy_apply_pretty_printer]
> 0x000000013F58A934 C:\src\repos\binutils-gdb.git\gdb\python\py-prettyprint.c:620:36 [gdbpy_apply_val_pretty_printer(extension_language_defn const*, value*, ui_file*, int, value_print_options const*, language_defn const*)]
> 0x000000013F493319 C:\src\repos\binutils-gdb.git\gdb\extension.c:488:51 [apply_ext_lang_val_pretty_printer(value*, ui_file*, int, value_print_options const*, language_defn const*)]
> 0x000000013F68B755 C:\src\repos\binutils-gdb.git\gdb\valprint.c:1028:47 [do_val_print]
> 0x000000013F5F5A06 C:\src\repos\binutils-gdb.git\gdb\stack.c:489:33 [print_frame_arg]
> 0x000000013F5F680C C:\src\repos\binutils-gdb.git\gdb\stack.c:893:22 [print_frame_args]
> 0x000000013F5F82E0 C:\src\repos\binutils-gdb.git\gdb\stack.c:1407:25 [print_frame]
> C:\src\repos\binutils-gdb.git\gdb\stack.c:1124:17 [print_frame_info(frame_print_options const&, frame_info*, int, print_what, int, int)]
> 0x000000013F5F887F C:\src\repos\binutils-gdb.git\gdb\stack.c:366:24 [print_stack_frame(frame_info*, int, print_what, int)]
> 0x000000013F4F5A97 C:\src\repos\binutils-gdb.git\gdb\infrun.c:8420:23 [print_stop_location]
> C:\src\repos\binutils-gdb.git\gdb\infrun.c:8436:25 [print_stop_event(ui_out*, bool)]
> 0x000000013F6630BD C:\src\repos\binutils-gdb.git\gdb\tui\tui-interp.c:99:19 [tui_on_normal_stop]
> 0x000000013F4F6F4F c:\msys64\mingw64\x86_64-w64-mingw32\include\c++\11.2.0\bits\std_function.h:560:9 [std::function<void (bpstats*, int)>::operator()(bpstats*, int) const]
> c:\src\repos\binutils-gdb.git\gdbsupport\observable.h:150:9 [gdb::observers::observable<bpstats*, int>::notify(bpstats*, int) const]
> C:\src\repos\binutils-gdb.git\gdb\infrun.c:8705:40 [normal_stop()]
> 0x000000013F502B42 C:\src\repos\binutils-gdb.git\gdb\infrun.c:4157:29 [fetch_inferior_event()]
> 0x000000013F3885FA C:\src\repos\binutils-gdb.git\gdb\async-event.c:335:31 [check_async_event_handlers()]
> 0x000000013F795901 C:\src\repos\binutils-gdb.git\gdbsupport\event-loop.cc:216:37 [gdb_do_one_event()]
> 0x000000013F64CD5B C:\src\repos\binutils-gdb.git\gdb\top.c:529:27 [wait_sync_command_done()]
> 0x000000013F64D650 C:\src\repos\binutils-gdb.git\gdb\top.c:546:28 [maybe_wait_sync_command_done(int)]
> C:\src\repos\binutils-gdb.git\gdb\top.c:687:36 [execute_command(char const*, int)]
> 0x000000013F5213BB C:\src\repos\binutils-gdb.git\gdb\main.c:523:15 [catch_command_errors]
> 0x000000013F5214E5 C:\src\repos\binutils-gdb.git\gdb\main.c:618:30 [execute_cmdargs]
> 0x000000013F524693 C:\src\repos\binutils-gdb.git\gdb\main.c:1322:19 [captured_main_1]
> 0x000000013F5250FC C:\src\repos\binutils-gdb.git\gdb\main.c:1343:19 [captured_main]
> C:\src\repos\binutils-gdb.git\gdb\main.c:1368:21 [gdb_main(captured_main_args*)]
> 0x000000013FA3C146 C:\src\repos\binutils-gdb.git\gdb\gdb.c:32:19 [main]
> 0x000000013F351430 C:\gcc\src\mingw-w64-v8.0.2\mingw-w64-crt\crt\crtexe.c:345:15 [__tmainCRTStartup]
> 0x000000013F3515B5 C:\gcc\src\mingw-w64-v8.0.2\mingw-w64-crt\crt\crtexe.c:220:9 [mainCRTStartup]
Should I not be even attempting to use gdb.parse_and_eval('$__cat->name()') like that in a printer? Is there a better way to call a virtual member function given a pointer to a polymorphic object? (In reply to Jonathan Wakely from comment #2) > Should I not be even attempting to use gdb.parse_and_eval('$__cat->name()') > like that in a printer? Is there a better way to call a virtual member > function given a pointer to a polymorphic object? If you need to call it, then I don't think there is a better way. I would only use a function call in a pretty printer if there is no way to avoid it, even more so because it wouldn't work for core files. (In reply to Jonathan Wakely from comment #2) > Should I not be even attempting to use gdb.parse_and_eval('$__cat->name()') > like that in a printer? Is there a better way to call a virtual member > function given a pointer to a polymorphic object? I agree with Hannes: you should ideally not call a function in the pretty printer so that it works with core files. But it's still a GDB bug, if someone chooses to do it, it should work. OK, thanks. I suppose I could just use the class name instead of calling the name() member. I think my preference would be to try to call the function, and if that returns a char* then use that, and if it fails (e.g. because there's no inferior process, just a core) then use the class name. But of course to gracefully find out if calling the function is possible, it needs to not crash :-) For the std:: error categories I can actually avoid calling the name() function, because GDB can look up the address of the private symbol and compare the _M_cat pointer to that. gdb.parse_and_eval('_ZN12_GLOBAL__N_124system_category_instanceE.obj').address But for user-defined custom categories, I'd like to be able to print the name. I'll see what I can do to avoid calling the function. Thanks for the guidance. I have managed to reproduce the crash without using the pretty printer. In fact, all I need to reproduce is: gdb -q ec -ex start -ex n -ex s -ex bt and it crashes using backtrace, up or finish. What I found while trying to debug this is that there is a corruption in the linked list, creating the following entry: 1: *this_frame = {level = 0, pspace = 0x0, aspace = 0x26b6d00, prologue_cache = 0x0, unwind = 0x0, prev_arch = {p = false, arch = 0x0}, prev_pc = {status = CC_UNKNOWN, masked = false, value = 0x0}, prev_func = {addr = 0x40113c, status = CC_VALUE}, this_id = {p = frame_id_status::COMPUTED, value = {stack_addr = 0x0, code_addr = 0x0, special_addr = 0x0, stack_status = FID_STACK_SENTINEL, code_addr_p = 0, special_addr_p = 1, artificial_depth = 0}}, base = 0x0, base_cache = 0x0, next = 0x288fee0, prev_p = true, prev = 0x288ffb0, stop_reason = UNWIND_NO_REASON, stop_string = 0x0} (top-gdb) p this_frame $2 = (frame_info *) 0x288fee0 I haven't tracked down where this is happening, but my guess is somewhere during the step process. The master branch has been updated by Bruno Larsen <blarsen@sourceware.org>: https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=daaf7acf47a12d10459060dca5500b63273cd683 commit daaf7acf47a12d10459060dca5500b63273cd683 Author: Bruno Larsen <blarsen@redhat.com> Date: Tue Feb 22 11:44:44 2022 -0300 [gdb/testsuite] test a function call by hand from pretty printer The test case added here is testing the bug gdb/28856, where calling a function by hand from a pretty printer makes GDB crash. There are 6 mechanisms to trigger this crash in the current test, using the commands backtrace, up, down, finish, step and continue. Since the failure happens because of use-after-free (more details below) the tests will always have a chance of passing through sheer luck, but anecdotally they seem to fail all of the time. The reason GDB is crashing is a use-after-free problem. The above mentioned functions save a pointer to the current frame's information, then calls the pretty printer, and uses the saved pointer for different reasons, depending on the function. The issue happens because call_function_by_hand needs to reset the obstack to get the current frame, invalidating the saved pointer. Created attachment 14034 [details]
Test case
This test case used to be in the tree (with some setup_kfails), but it still causes problems when GDB is built with ASan. I removed it from the tree and am attaching it here, so it can be merged back when we have a corresponding fix.
The master branch has been updated by Bruno Larsen <blarsen@sourceware.org>: https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=c29a6445a981cee5e8eebe3617ef5c049fd3409f commit c29a6445a981cee5e8eebe3617ef5c049fd3409f Author: Bruno Larsen <blarsen@redhat.com> Date: Mon Jul 25 14:06:37 2022 -0300 gdb/frame: Add reinflation method for frame_info_ptr Currently, despite having a smart pointer for frame_infos, GDB may attempt to use an invalidated frame_info_ptr, which would cause internal errors to happen. One such example has been documented as PR python/28856, that happened when printing frame arguments calls an inferior function. To avoid failures, the smart wrapper was changed to also cache the frame id, so the pointer can be reinflated later. For this to work, the frame-id stuff had to be moved to their own .h file, which is included by frame-info.h. Frame_id caching is done explicitly using the prepare_reinflate method. Caching is done manually so that only the pointers that need to be saved will be, and reinflating has to be done manually using the reinflate method because the get method and the -> operator must not change the internals of the class. Finally, attempting to reinflate when the pointer is being invalidated causes the following assertion errors: check_ptrace_stopped_lwp_gone: assertion `lp->stopped` failed. get_frame_pc: Assertion `frame->next != NULL` failed. As for performance concerns, my personal testing with `time make chec-perf GDB_PERFTEST_MODE=run` showed an actual reduction of around 10% of time running. This commit also adds a testcase that exercises the python/28856 bug with 7 different triggers, run, continue, step, backtrace, finish, up and down. Some of them can seem to be testing the same thing twice, but since this test relies on stale pointers, there is always a chance that GDB got lucky when testing, so better to test extra. Regression tested on x86_64, using both gcc and clang. Approved-by: Tom Tomey <tom@tromey.com> The commit mentioned by cvs-commit fixes this issue. *** Bug 24393 has been marked as a duplicate of this bug. *** *** Bug 17437 has been marked as a duplicate of this bug. *** |