[lldb-dev] Setting breakpoint on file and function name doesn't work as expected.

Jim Ingham via lldb-dev lldb-dev at lists.llvm.org
Thu Nov 7 13:35:00 PST 2019


Note first, "break set -f foo.c -l 10" and "break set -f foo.c -n bar" are two very different breakpoints.  In the first case, we are looking for a specific file & line combination in the line table or inlined functions info.  In the latter case we are searching for a function names bar, and then restricting the results to the matching functions whose definition is in foo.c.  It seemed neat to not make "break set" be a three-level command, like:

(lldb) break set file-and-line
(lldb) break set function-name
etc...

and use options for everything, but it does make it a little harder to document the cases where one option means something slightly different when used in combination with one or the other option.

Anyway, so setting a breakpoint by file name and function name was meant to support the case where you have:

$ cat foo.c
static int baz() {}
...
$ cat bar.c
static int baz() {}
...

and you want to limit the breakpoint to only the version of foo that was in foo.c.  You would do:

(lldb) b s -n baz -f foo.c
Breakpoint 1: where = a.out`baz + 4 at foo.c:5:10, address = 0x0000000100000ee4

In that case searching in the CU makes sense, and is efficient.

Adding the name to a function name breakpoint is really only useful if you have multiple different instances of the same name that you need to disambiguate.  In your example, you gain nothing by providing the header file name in which the symbol is defined.

I don't think I designed the behavior for when the colliding functions are actually defined in separate header files - I don't remember considering the possibility.  So what's happening is just an accidental outcome. It would certainly be possible, though a little trickier to do the filtering based on defining header.   You will have to look for the symbol and then chase down its declaration records to make sure they are in the right .h file, and also look for any inlined instances.  You probably also want to take some care not to regress the performance of the case where the function is defined in a CU, since that's the more common usage.

Jim

> On Nov 4, 2019, at 7:16 AM, Konrad Kleine via lldb-dev <lldb-dev at lists.llvm.org> wrote:
> 
> I read the LLDB troubleshooting page [1] and found interesting quotes:
> 
> > When setting breakpoints in implementation source files (.c, cpp, cxx, .m, .mm, etc), LLDB by
> > default will only search for compile units whose filename matches. 
> > [...]
> >   % echo "settings set target.inline-breakpoint-strategy always" >> ~/.lldbinit
> > This tells LLDB to always look in all compile units and search for breakpoint locations
> > by file and line even if the implementation file doesn’t match. Setting breakpoints in header
> > files always searches all compile units because inline functions are commonly defined in
> > header files and often cause multiple breakpoints to have source line information that matches
> > many header file paths.
> 
> In my email before I did this
> 
> $ lldb -x -b -o "breakpoint set --file foo.h --name foo" ./a.out
> 
> I now added the breakpoint strategy and ran the above command without the -x in order to pick up
> the LLDB init code. Still no luck.
> 
> [1]: https://lldb.llvm.org/use/troubleshooting.html#troubleshooting
> 
> Am Mo., 4. Nov. 2019 um 13:56 Uhr schrieb Konrad Kleine <kkleine at redhat.com>:
> Hello,
> 
> I noticed this behavior for LLDB under Linux when setting a breakpoint on a file and a function name:
> 
> When doing "breakpoint set --file <filename> --name <function-name>", the <filename> is that of the compile unit (CU) and not necessarily where the function is defined. This is not what an end-user expects.
> 
> Take this simple example program:
> 
> $ cat foo.h 
> int foo(){ return 42; }
> 
> $ cat main.c
> #include "foo.h"
> int main(){return foo();}
> 
> $ clang -g main.c
> 
> As you can see, the function foo is defined in foo.h so it seems natural to set a breakpoint on foo.h, doesn't it?
> 
> $ lldb -x -b -o "breakpoint set --file foo.h --name foo" ./a.out 
> (lldb) target create "./a.out"
> Current executable set to './a.out' (x86_64).
> (lldb) breakpoint set --file foo.h --name foo
> Breakpoint 1: no locations (pending).
> WARNING:  Unable to resolve breakpoint to any actual locations.
> 
> Apparently, LLDB cannot find the symbol like this. Let's try the only other file that we have in the project:
> 
> $ lldb -x -b -o "breakpoint set --file main.c --name foo" ./a.out
> (lldb) target create "./a.out"
> Current executable set to './a.out' (x86_64).
> (lldb) breakpoint set --file main.c --name foo
> Breakpoint 1: where = a.out`foo + 4 at foo.h:1:12, address = 0x0000000000401114
> 
> Isn't that remarkable? LLDB uses main.c as the file to search in but then finds it in foo.h.
> 
> Let's recall what the parameters --file and --name mean:
> 
>        -n <function-name> ( --name <function-name> )
>             Set the breakpoint by function name.  Can be repeated multiple times to make one breakpoint for multiple names
> 
>        -f <filename> ( --file <filename> )
>             Specifies the source file in which to set this breakpoint.  Note, by default lldb only looks for files that are #included if they use the standard include file extensions.  To
>             set breakpoints on .c/.cpp/.m/.mm files that are #included, set target.inline-breakpoint-strategy to "always".
> 
> Let's check if setting the target.inline-breakpoint strategy to "always" changes something:
> 
> $ lldb -x -b  -o "settings set target.inline-breakpoint-strategy always" -o "breakpoint set --file foo.h --name foo" ./a.out
> (lldb) target create "./a.out"
> Current executable set to './a.out' (x86_64).
> (lldb) settings set target.inline-breakpoint-strategy always
> (lldb) breakpoint set --file foo.h --name foo
> Breakpoint 1: no locations (pending).
> WARNING:  Unable to resolve breakpoint to any actual locations.
> 
> No, it didn't change anything.
> 
> The only evidence for my assumption that LLDB uses the CU's name for --file is the DWARF dump:
> 
> $ llvm-dwarfdump a.out 
> a.out:	file format ELF64-x86-64
> 
> .debug_info contents:
> 0x00000000: Compile Unit: length = 0x00000060 version = 0x0004 abbr_offset = 0x0000 addr_size = 0x08 (next unit at 0x00000064)
> 
> 0x0000000b: DW_TAG_compile_unit
>               DW_AT_producer	("clang version 8.0.0 (Fedora 8.0.0-3.fc30)")
>               DW_AT_language	(DW_LANG_C99)
>               DW_AT_name	("main.c")
>               DW_AT_stmt_list	(0x00000000)
>               DW_AT_comp_dir	("/home/kkleine")
>               DW_AT_low_pc	(0x0000000000401110)
>               DW_AT_high_pc	(0x000000000040113a)
> 
> 0x0000002a:   DW_TAG_subprogram
>                 DW_AT_low_pc	(0x0000000000401110)
>                 DW_AT_high_pc	(0x000000000040111b)
>                 DW_AT_frame_base	(DW_OP_reg6 RBP)
>                 DW_AT_name	("foo")
>                 DW_AT_decl_file	("/home/kkleine/./foo.h")
>                 DW_AT_decl_line	(1)
>                 DW_AT_type	(0x0000005c "int")
>                 DW_AT_external	(true)
> 
> 0x00000043:   DW_TAG_subprogram
>                 DW_AT_low_pc	(0x0000000000401120)
>                 DW_AT_high_pc	(0x000000000040113a)
>                 DW_AT_frame_base	(DW_OP_reg6 RBP)
>                 DW_AT_name	("main")
>                 DW_AT_decl_file	("/home/kkleine/main.c")
>                 DW_AT_decl_line	(2)
>                 DW_AT_type	(0x0000005c "int")
>                 DW_AT_external	(true)
> 
> 0x0000005c:   DW_TAG_base_type
>                 DW_AT_name	("int")
>                 DW_AT_encoding	(DW_ATE_signed)
>                 DW_AT_byte_size	(0x04)
> 
> As you can see, the DWARF is very small and simply. The function foo has a DW_AT_decl_file which is probably used to report the breakpoint location but for the actual filtering, it seems as if the CU is crucial for the --file argument.
> 
> The only reasonable implementation for --file to me seems to be when combined with the line number:
> 
> $ lldb -x -b  -o "breakpoint set --file foo.h --line 1" ./a.out
> (lldb) target create "./a.out"
> Current executable set to './a.out' (x86_64).
> (lldb) breakpoint set --file foo.h --line 1
> Breakpoint 1: where = a.out`foo + 4 at foo.h:1:12, address = 0x0000000000401114
> 
> This works as expected.
> 
> For myself I think that the --file --name combination works not like an end-user expects because in bigger projects you typically look at the definition of foo and want to pause, when execution reaches this. You don't care if a function is inlined or in which CU the function is located. Moreover I think the DWARF actually supports more than enough information with the DW_TAG_subprogram DIE's attributes for the file and line number. Enough to filter by file and line number.
> 
> IMHO we should come up with a very strong argument to justify that --file limits by a CU's DW_AT_name. To me the only reasonable argument is speed. But speed doesn't justify such a drastic limitation. Shouldn't we rather change what --file currently does and keep the old behavior when using --cu instead of --file?
> 
> - Konrad
> 
> 
> 
> _______________________________________________
> lldb-dev mailing list
> lldb-dev at lists.llvm.org
> https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-dev



More information about the lldb-dev mailing list