How to set source line breakpoint using BreakpointCreateByLocation?

Hi,

I am writing a python script to set source line breakpoint in ObjC on Mac OSX. But self.debugger.GetSelectedTarget().BreakpointCreateByLocation(“EATAnimatedView.m”, line) always fail. Any ideas?

Also, can I use full path instead of file basename? In lldb, I found “b /Users/jeffreytan/fbsource/fbobjc/Apps/Internal/MPKEats/MPKEats/View/EATAnimatedView.m:21” will fail to bind but “b EATAnimatedView.m:21” will succeed.

Traceback (most recent call last):
File “/Users/jeffreytan/fbsource/fbobjc/Tools/Nuclide/pkg/nuclide/debugger/lldb/scripts/chromedebugger.py”, line 69, in _generate_response
params=message.get(‘params’, {}),
File “/Users/jeffreytan/fbsource/fbobjc/Tools/Nuclide/pkg/nuclide/debugger/lldb/scripts/handler.py”, line 42, in handle
return self._domains[domain_name].handle(method_name, params)
File “/Users/jeffreytan/fbsource/fbobjc/Tools/Nuclide/pkg/nuclide/debugger/lldb/scripts/handler.py”, line 106, in handle
return self._handlersmethod
File “/Users/jeffreytan/fbsource/fbobjc/Tools/Nuclide/pkg/nuclide/debugger/lldb/scripts/handler.py”, line 56, in _handler_wrapper
ret = func(self, params)
File “/Users/jeffreytan/fbsource/fbobjc/Tools/Nuclide/pkg/nuclide/debugger/lldb/scripts/debugger.py”, line 248, in setBreakpointByUrl
int(params[‘lineNumber’]) + 1)
File “/Users/jeffreytan/fbsource/fbobjc/Tools/Nuclide/pkg/nuclide/debugger/lldb/scripts/debugger.py”, line 283, in _set_breakpoint_by_filespec
breakpoint = self.debugger.GetSelectedTarget().BreakpointCreateByLocation(filespec, line)
File “/Applications/Xcode.app/Contents/Developer/…/SharedFrameworks/LLDB.framework/Resources/Python/lldb/init.py”, line 8650, in BreakpointCreateByLocation
return _lldb.SBTarget_BreakpointCreateByLocation(self, *args)
NotImplementedError: Wrong number of arguments for overloaded function ‘SBTarget_BreakpointCreateByLocation’.
Possible C/C++ prototypes are:
BreakpointCreateByLocation(lldb::SBTarget *,char const *,uint32_t)
BreakpointCreateByLocation(lldb::SBTarget *,lldb::SBFileSpec const &,uint32_t)

Hi,

I am writing a python script to set source line breakpoint in ObjC on Mac OSX. But self.debugger.GetSelectedTarget().BreakpointCreateByLocation("EATAnimatedView.m", line) always fail. Any ideas?

As long as you have a selected target, this should work as long as you have debug info that matches.

Also, can I use full path instead of file basename?

Yes you can, but it must match exactly if you use the full path.

In lldb, I found "b /Users/jeffreytan/fbsource/fbobjc/Apps/Internal/MPKEats/MPKEats/View/EATAnimatedView.m:21" will fail to bind but "b EATAnimatedView.m:21" will succeed.

This is usually because you have a makefile build system that is creating debug info that doesn't contain a path that matches. When you launch a compiler, it can often end up with different paths than what you think you have. So set the breakpoint by basename first, then do:

(lldb) b main.c:12
Breakpoint 1: where = a.out`main + 70 at main.c:12, address = 0x0000000100000b96
(lldb) breakpoint list --verbose
Current breakpoints:
1: file = 'main.c', line = 12
    1.1:
      module = /Volumes/work/gclayton/Documents/src/args/a.out
      compile unit = main.c
      function = main
      location = /Volumes/work/gclayton/Documents/src/args/main.c:12
      address = a.out[0x0000000100000b96]
      resolved = false
      hit count = 0

You will see the full path to your source file in the "location" value. You will probably notice that the path is different. We try to take care of removing and extra "../useless_dir" things from the paths and still make things match, but you might have a case that we aren't handling. Let me know what you see when you set the breakpoint by basename.

Ooops, I accidentally dropped lldb mail list.
I have chatted this long relative path issue with our buck build team, they seem to do some kind of post-processing for the symbol files but failed to run another scripts to resolve the processed paths back into absolute path. And they are unable to fix this in short time. :frowning:

I am building a workaround for this issue:
I am building a middle indirection map between debugger UI and lldb. Whenever I enumerate a SBFileSpec a source line, I get the source file long relative path from it and add basepath of the build project then normalize into an absolute path, then store absolute_path->SBFileSpec mapping into the map, then send the absolute path to debugger UI. When the debugger UI is trying to set source line breakpoint, I got the absolute path back from debugger UI and use the middle indirection map to find corresponding SBFileSpec for that source file. Finally, I used the SBFileSpec to set source line breakpoint.
The assumption is that as long as I used the same SBFileSpec from lldb, I should use it to set source line breakpoint successfully.
However, it turns out this still does not work. The breakpoints are listed as pending:

break list
Current breakpoints:
1: file = ‘.//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/App/main.m’, line = 9, locations = 0 (pending)
2: file = ‘.//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View/EATAnimatedView.m’, line = 20, locations = 0 (pending)

Questions:
Isn’t lldb treating this long relative path as absolute path so as long as this long path is used to set breakpoint it should work? Any idea why this workaround approach does not work?

Jeffrey

Not sure why we aren't treating this as a relative path. Probably because of the 1000 '/' characters. Feel free to dig into lldb_private::FileSpec and play around and see where things are going wrong.

You can probably fix LLDB, when it resolves paths, to clean this up, but we typically don't resolve paths that we find in debug info when we run into it. Why? Because if you have DWARF info that contains paths that map to 10000 files that exist on network file system mounts, we would need to resolve all of those paths as we parse the line tables and calling stat() on all these files take a TON of time. So we won't resolve paths we find in debug info typically. We could try and run a path resolving function that doesn't call stat() to fix up an really bad paths like the ones that your build system is making, but we don't have support for that right now due to the network file system stat() issue.

Greg

Hi Greg,

I am not sure if I understand the behavior here: the long relative file path from our build system does not exist on file system, do you mean lldb will always try to resolve this relative path to a real file on file system using stat() call? And it will fail to bind the breakpoint if can’t find the resolved file path?

Per my testing, I was able to use #1. “b EATAnimatedView.m:33” to bind breakpoint, but not:
#2, “b .//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View/EATAnimatedView.m:20”

break list --verbose
Current breakpoints:
2: file = ‘.//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View/EATAnimatedView.m’, line = 20

3: file = ‘EATAnimatedView.m’, line = 33
3.1:
module = /Users/jeffreytan/Library/Developer/CoreSimulator/Devices/32967F60-A4C3-43DC-ACA8-92D819413362/data/Containers/Bundle/Application/96DA24F5-E35D-402F-B4B7-1C5BBD40B270/MPKEats.app/MPKEats
compile unit = EATAnimatedView.m
function = -[EATAnimatedView initWithFrame:imageNames:animationDuration:repeatCount:touchEnabled:]
location = ./////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View/EATAnimatedView.m:33
address = 0x000000010a1ff798
resolved = true
hit count = 0

Do you mean #2 should work? I think I am a bit vague on how source breakpoint is supposed to work. What is the algorithm to successfully bind breakpoint here?

Jeffrey

Hi Greg,

I am not sure if I understand the behavior here: the long relative file path from our build system does *not* exist on file system, do you mean lldb will always try to resolve this relative path to a *real* file on file system using stat() call?

No it doesn't do that and that is why you path remains as is.

And it will fail to bind the breakpoint if can't find the resolved file path?

No it shouldn't if you set it by basename. If it does fail to set the breakpoint using "EATAnimatedView.m" as the filename, then it is a bug.

Per my testing, I was able to use #1. "b EATAnimatedView.m:33" to bind breakpoint, but not:
#2, "b .//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View/EATAnimatedView.m:20"

That is interesting, and you will need to track down why this is happening. Obviously we are expecting this kind of path and something is going wrong.

break list --verbose
Current breakpoints:
2: file = './/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View/EATAnimatedView.m', line = 20

3: file = 'EATAnimatedView.m', line = 33
    3.1:
      module = /Users/jeffreytan/Library/Developer/CoreSimulator/Devices/32967F60-A4C3-43DC-ACA8-92D819413362/data/Containers/Bundle/Application/96DA24F5-E35D-402F-B4B7-1C5BBD40B270/MPKEats.app/MPKEats
      compile unit = EATAnimatedView.m
      function = -[EATAnimatedView initWithFrame:imageNames:animationDuration:repeatCount:touchEnabled:]
      location = ./////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View/EATAnimatedView.m:33
      address = 0x000000010a1ff798
      resolved = true
      hit count = 0

Do you mean #2 should work?

I would expect #2 to work, but not surprised it doesn't.

I think I am a bit vague on how source breakpoint is supposed to work. What is the algorithm to successfully bind breakpoint here?

What we currently do is break all file paths up into a basename (EATAnimatedView.m) and a directory (.//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View).

We break up filenames like this because the user will rarely type full paths in commands. IDEs will often give us full paths to files, but users rarely do. So to efficiently store files and make them easy to compare, we split the path up into two strings. Each string gets constified by being placed into a ConstString object that uniques the string and gives out the same pointer for each string it uniques so we can use pointer comparisons to compare two strings. FileSpec objects contain:

class FileSpec
{
protected:
    //------------------------------------------------------------------
    // Member variables
    //------------------------------------------------------------------
    ConstString m_directory; ///< The uniqued directory path
    ConstString m_filename; ///< The uniqued filename path
    mutable bool m_is_resolved; ///< True if this path has been resolved.
    PathSyntax m_syntax; ///< The syntax that this path uses (e.g. Windows / Posix)
};

So we can easily compare strings. You should probably step through some code and figure out why things are not matching up. The FileSpec class has some functions that try to clean up paths without calling stat:

    void
    FileSpec::NormalizePath ();

    void
    FileSpec::RemoveBackupDots (const ConstString &input_const_str, ConstString &result_const_str);

And a few others. They might be causing the problems. You will need to debug this if you wish to figure out what is going wrong.

A few tests that I ran include:

(lldb) script
Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.

f = lldb.SBFileSpec('.//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View/EATAnimatedView.m', False)
print f.GetFilename()

EATAnimatedView.m

print f.GetDirectory()

.//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View

f = lldb.SBFileSpec('.//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View/EATAnimatedView.m', True)
print f.GetFilename()

EATAnimatedView.m

print f.GetDirectory() .//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Apps/Internal/MPKEats/MPKEats/View

So we seem to split the path up correctly in both cases where we don't resolve and resolve the path.

Thanks for the info, I will debug this.
One more quick question related to this: in full path breakpoint case(IDE scenario), do we compare both file name and directory path exact match and bind breakpoint successfully? I ask this because of the breakpoint issue I found:

  1. I have a symbolic link folder “/home/jeffreytan/foo/” which points to “/data/users/jeffreytan/bar/foo”.
  2. If I built cpp file in /home/jeffreytan/foo/, and try to set breakpoint using real path “b /data/users/jeffreytan/bar/foo/main.cpp:10” it will fail to bind.
  3. While “b /home/jeffreytan/foo//main.cpp:10” binds fine, vise verse.

I can’t control how users open/build file in our IDE, they may choose to open/build from symbolic link folder or real path folder. What is the general suggestion to deal with symbolic link differences?

Jeffrey

Thanks for the info, I will debug this.
One more quick question related to this: in full path breakpoint case(IDE scenario), do we compare both file name and directory path exact match and bind breakpoint successfully? I ask this because of the breakpoint issue I found:
1. I have a symbolic link folder "/home/jeffreytan/foo/" which points to "/data/users/jeffreytan/bar/foo".
2. If I built cpp file in /home/jeffreytan/foo/, and try to set breakpoint using real path "b /data/users/jeffreytan/bar/foo/main.cpp:10" it will fail to bind.
3. While "b /home/jeffreytan/foo//main.cpp:10" binds fine, vise verse.

Yep, that will fail for the reason that "/home/jeffreytan/foo" won't match "/data/users/jeffreytan/bar/foo" and we don't resolve paths on debug info because of the cost as we mentioned before.

I can't control how users open/build file in our IDE, they may choose to open/build from symbolic link folder or real path folder. What is the general suggestion to deal with symbolic link differences?

There isn't a good one right now. One idea is to try and track the project directory in your IDE, like "/home/jeffreytan/foo", and note that this is the base directory. Then have the IDE always set the breakpoint by basename ("main.cpp") and then filter the results out based on where the file is in the project directory. So if you have your main file in your project directory as "src/main.cpp", you would set a breakpoint in main.cpp, and then look at the locations that the breakpoint had and disable any locations that don't have a matching parent directory of "src"...

Greg

Thanks for the info. I will use a solution similar to that.

Jeffrey