[RFC] [gdb/breakpoints] Use gdbarch_addr_bits_remove in breakpoint_address_match
Tom de Vries
tdevries@suse.de
Fri Aug 30 08:25:49 GMT 2024
On 8/30/24 09:21, Luis Machado wrote:
> On 8/29/24 11:20, Tom de Vries wrote:
>> Consider a hello world exec on arm-linux (specifically debian, which is
>> relevant because it strips minimal symbols from shared libraries), compiled
>> with -marm:
>
> I suppose excessive stripping would be to blame here, right? The thumb-checking logic
> is really dependent on the symbol information so we can have mapping symbols. If
> those are not available, then it is hard to guess the thumbness correctly.
>
It is hard to guess indeed, but that doesn't mean that gdb doesn't try
to take care of that situation.
For instance, we have arm fallback-mode which:
...
controls GDB’s default behavior when the symbol table is not available.
...
>> ...
>> $ gcc -g -marm hello.c
>> ...
>> and started in gdb:
>> ...
>> $ gdb -q a.out -ex start
>> Reading symbols from a.out...
>> Temporary breakpoint 1 at 0x50c: file hello.c, line 6.
>> Starting program: a.out
>>
>> Temporary breakpoint 1, main () at hello.c:6
>> 6 printf ("hello\n");
>> (gdb)
>> ...
>>
>> The link register contains the address main returns to:
>> ...
>> (gdb) p /x $lr
>> $1 = 0xf7ec43db
>
> This is actually a thumb address with the LSB set (to indicate it is a thumb address).
>
I see, I got that mixed up, thanks for pointing that out.
> The procedure is to remove that bit internally and use the thumb address instead.
>
With which you currently throw away the information that it's a thumb
address.
>> ...
>> which is some thumb address in libc.
>>
>> We can step to it, but say we want to set a breakpoint on it, and continue.
>>
>> First, let's try with the non-thumb address:
>> ...
>> (gdb) b *0xf7ec43da
>> Breakpoint 2 at 0xf7ec43da
>> (gdb) c
>> Continuing.
>> hello
>>
>> Program received signal SIGSEGV, Segmentation fault.
>> 0xf7ec43c8 in ?? () from /lib/arm-linux-gnueabihf/libc.so.6
>> (gdb)
>> ...
>>
>> So what happened? In arm_breakpoint_kind_from_pc, arm_pc_is_thumb is called
>> which as last resort falls back to "arm fallback-mode auto", which (given that
>> we're in the frame of main, which is an arm frame) returns false.
>
> I find the above a bit strange. We clearly have a thumb address, so the fact gdb is
> going with a regular breakpoint is odd. This case should've worked without having
> to do anything extra.
>
Um, you mean because it's not 4-byte aligned:
...
(gdb) p /x 0xf7ec43da & 0x3 == 0
$5 = 0x0
...
?
That's not taken into account in arm_pc_is_thumb.
So, we could add that, but that would only work in half of the cases
(which is perhaps why it hasn't been added).
>> That is the wrong answer, so we're choosing the wrong kind of breakpoint and
>> things go downhill from there.
>>
>> We can workaround this by doing "set arm fallback-mode thumb", but say we
>> don't want that because that's a bad default for our current situation.
>>
>> So, let's now try with the thumb address:
>> ...
>> (gdb) b *0xf7ec43db
>> Breakpoint 2 at 0xf7ec43db
>> (gdb) c
>> Continuing.
>> hello
>> <hangs>
>> ...
>>
>
> The above works, but I don't think it is correct.
The breakpoint doesn't trigger, so I don't think that it works. The
proposed patch does make it work.
> All we're doing is providing a hint that
> a particular address is thumb by setting the LSB to 1, so gdb know what to do, but
> address 0xf7ec43db isn't really a thing.
>
I don't see it any different as assuming "arm fallback-mode thumb" for
one specific address.
Thanks,
- Tom
>> In this case, we get the correct breakpoint kind, but the breakpoint doesn't
>> trigger because the address doesn't match.
>>
>> More specifically, in handle_signal_stop we do:
>> ...
>> ecs->event_thread->set_stop_pc
>> (regcache_read_pc (get_thread_regcache (ecs->event_thread)));
>> ...
>> and regcache_read_pc calls gdbarch_addr_bits_remove so
>> ecs->event_thread->stop_pc () == 0xf7ec43da, and that's the address
>> that gets compared to breakpoint address 0xf7ec43db in
>> breakpoint_address_match.
>>
>> Fix this by using gdbarch_addr_bits_remove in breakpoint_address_match, such
>> that we get:
>> ...
>> (gdb) c
>> Continuing.
>> hello
>>
>> Breakpoint 2, 0xf7ec43da in ?? () from /lib/arm-linux-gnueabihf/libc.so.6
>> (gdb)
>> ...
>>
>> Likewise in breakpoint_address_match_range.
>>
>> Note: if we would hoist the call to gdbarch_addr_bits_remove in
>> arm_adjust_breakpoint_address like this:
>> ...
>> map_type = arm_find_mapping_symbol (bpaddr, &boundary);
>> +
>> + bpaddr = gdbarch_addr_bits_remove (gdbarch, bpaddr);
>> +
>> if (map_type == 0)
>> /* Thumb-2 code must have mapping symbols to have a chance. */
>> return bpaddr;
>>
>> - bpaddr = gdbarch_addr_bits_remove (gdbarch, bpaddr);
>> ...
>> we would not need to change breakpoint_address_match, but doing so would
>> merely transform the problem into the non-thumb address case shown above, and
>> we'd run into a SIGSEGV or SIGILL or some such.
>>
>> If however we make the arm target:
>> - remember the addr bits it removed, and
>> - recall those in arm_pc_is_thumb
>> then his approach would work and there'd be no need for changes outside the
>> target.
>>
>> However, note that say you make a mistake and first do break *0xf00f001, while
>> the address is not thumb and you should have used *0xf00f000, then a subsequent
>> "break *0xf00f000" also gets a thumb breakpoint. So you'd need a means to
>> correct these kind of mistakes and make this caching explicit to the user,
>> which is not great.
>>
>> Tested on arm-linux.
>> ---
>> gdb/breakpoint.c | 26 +++++--
>> .../gdb.arch/thumb-address-breakpoint.c | 36 ++++++++++
>> .../gdb.arch/thumb-address-breakpoint.exp | 71 +++++++++++++++++++
>> 3 files changed, 127 insertions(+), 6 deletions(-)
>> create mode 100644 gdb/testsuite/gdb.arch/thumb-address-breakpoint.c
>> create mode 100644 gdb/testsuite/gdb.arch/thumb-address-breakpoint.exp
>>
>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>> index 17bd627f867..1558627c9a3 100644
>> --- a/gdb/breakpoint.c
>> +++ b/gdb/breakpoint.c
>> @@ -7288,9 +7288,16 @@ int
>> breakpoint_address_match (const address_space *aspace1, CORE_ADDR addr1,
>> const address_space *aspace2, CORE_ADDR addr2)
>> {
>> - return ((gdbarch_has_global_breakpoints (current_inferior ()->arch ())
>> - || aspace1 == aspace2)
>> - && addr1 == addr2);
>> + struct gdbarch *gdbarch = current_inferior ()->arch ();
>> +
>> + bool same_aspace = (aspace1 == aspace2
>> + || gdbarch_has_global_breakpoints (gdbarch));
>> + if (!same_aspace)
>> + return false;
>> +
>> + addr1 = gdbarch_addr_bits_remove (gdbarch, addr1);
>> + addr2 = gdbarch_addr_bits_remove (gdbarch, addr2);
>> + return addr1 == addr2;
>> }
>>
>> /* Returns true if {ASPACE2,ADDR2} falls within the range determined by
>> @@ -7304,9 +7311,16 @@ breakpoint_address_match_range (const address_space *aspace1,
>> int len1, const address_space *aspace2,
>> CORE_ADDR addr2)
>> {
>> - return ((gdbarch_has_global_breakpoints (current_inferior ()->arch ())
>> - || aspace1 == aspace2)
>> - && addr2 >= addr1 && addr2 < addr1 + len1);
>> + struct gdbarch *gdbarch = current_inferior ()->arch ();
>> +
>> + bool same_aspace = (aspace1 == aspace2
>> + || gdbarch_has_global_breakpoints (gdbarch));
>> + if (!same_aspace)
>> + return false;
>> +
>> + addr1 = gdbarch_addr_bits_remove (gdbarch, addr1);
>> + addr2 = gdbarch_addr_bits_remove (gdbarch, addr2);
>> + return addr2 >= addr1 && addr2 < addr1 + len1;
>> }
>>
>> /* Returns true if {ASPACE,ADDR} matches the breakpoint BL. BL may be
>> diff --git a/gdb/testsuite/gdb.arch/thumb-address-breakpoint.c b/gdb/testsuite/gdb.arch/thumb-address-breakpoint.c
>> new file mode 100644
>> index 00000000000..30e09b295dc
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/thumb-address-breakpoint.c
>> @@ -0,0 +1,36 @@
>> +/* Copyright 2024 Free Software Foundation, Inc.
>> +
>> + This file is part of GDB.
>> +
>> + 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/>. */
>> +
>> +__attribute__((target("arm")))
>> +static void
>> +bar (void)
>> +{
>> +}
>> +
>> +__attribute__((target("thumb")))
>> +void
>> +foo (void)
>> +{
>> + bar ();
>> +}
>> +
>> +int
>> +main (void)
>> +{
>> + foo ();
>> + return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.arch/thumb-address-breakpoint.exp b/gdb/testsuite/gdb.arch/thumb-address-breakpoint.exp
>> new file mode 100644
>> index 00000000000..a024f3728b1
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/thumb-address-breakpoint.exp
>> @@ -0,0 +1,71 @@
>> +# Copyright 2024 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/>.
>> +
>> +require is_aarch32_target
>> +
>> +standard_testfile
>> +
>> +if { [build_executable "failed to prepare" $testfile $srcfile {nodebug}] } {
>> + return -1
>> +}
>> +
>> +# Generate $binfile.stripped.
>> +set objcopy_program [gdb_find_objcopy]
>> +set objcopy_args "--strip-all --keep-symbol=bar"
>> +set cmd "$objcopy_program $objcopy_args $binfile $binfile.stripped"
>> +set result [catch "exec $cmd" output]
>> +if { $result != 0 } {
>> + verbose -log "result is $result"
>> + verbose -log "output is $output"
>> + return -1
>> +}
>> +
>> +clean_restart $binfile.stripped
>> +
>> +# We're able to set a breakpoint on bar because we didn't strip that symbol.
>> +# Bar is an arm function. This prevents "arm fallback-mode auto" from
>> +# magically declaring addresses to be thumb, at least while we're in that
>> +# frame.
>> +if { ![runto bar] } {
>> + return -1
>> +}
>> +
>> +# Get the link register, pointing to foo, a thumb function.
>> +set lr ""
>> +gdb_test_multiple {p /x $lr} "" {
>> + -re -wrap "= ($hex)" {
>> + set lr $expect_out(1,string)
>> + pass $gdb_test_name
>> + }
>> +}
>> +
>> +if { $lr == "" } {
>> + return -1
>> +}
>> +
>> +# Assert that $lr is a thumb address.
>> +gdb_assert { [expr $lr & 1 == 1] }
>> +
>> +# Set a breakpoint on $lr.
>> +gdb_breakpoint "*$lr"
>> +
>> +# With unstripped $binfile, the thumb bit would have been dropped by
>> +# arm_adjust_breakpoint_address. Check that the breakpoint address still
>> +# contains the thumb bit. This was a necessary condition to trigger the hang.
>> +set re_lr [regsub 0x0* $lr ""]
>> +gdb_test "info break" $re_lr.*
>> +
>> +# Now check that the breakpoint actually works. This used to hang.
>> +gdb_continue_to_breakpoint "foo"
>>
>> base-commit: 8ee4561d38f2b35cc5068ad3b39f839c2847b5d4
>
More information about the Gdb-patches
mailing list