How can I find the address of a stack trace in LLDB for iOS
When I get a crash report, the offending part of my code will sometimes look like this, instead of showing me the actual line number, even though the crash report is symbolicated:
-[ViewController myMethod:] + 47
In order to debug this, I need to know what line of my code this represents so that I can visually inspect it, set a breakpoint, etc.
What is a good way to get the address of a method plus offset, as shown above, using LLDB?
NOTE: this question is NOT a duplicate of how to read a crash report. I know how to read a crash report. I am asking very specifically how to get the corresponding line using LLDB. Nothing in the other answers shows how to do that. They are quite verbose and go into all kinds of things about dealing with crash reports and debugging in general, but don't show what the specific steps on LLDB are. Please do not duplicate this bug.
Solution 1:
Here is something I found that worked:
First you need to find the address of the method itself.
image lookup -v -F "-[ViewController myMethod:]"
in the result you will see a lot of info, but the range part will give you want you want
... range = [0x000708c0-0x00070c6c) ...
(where 0x000708c0 is address of method)
Now to add the given offset of 47, just use LLDB to do that math for you:
(lldb) p/x 0x000708c0 + 47
(int) $0 = 0x000708ef
and you get your answer, the offending line is on 0x000708ef
Or better yet, based on Jason Molenda's answer, is to just go straight to the code listing, which will show the line number:
(lldb) source list -a `0x000708c0 + 47`
EDIT: improved based on the answer from Jason Molenda
Solution 2:
[note this ONLY works if you save Archives in XCode of all the builds you released]
Information you need to collect first:
- APPNAME: the short name of your app as seen in the Archive directory (typically the XCode Target name; you will see it immediately when you look at the Archive directory in Finder below).
-
CRASHING_FUNCTION_NAME: name of function shown in useless Apple backtrace (in the OP's example,
-[ViewController myMethod:]
) -
ARCH: architecture of device that crashed. Most likely the right value is either
armv7
orarm64
. If you don't know, try both.
Ok here are the steps:
- In XCode go to Window...Organizer...Archives
- Right-click on the Archive for the release the crashing user has, and choose Show in Finder
- open a Terminal shell and
cd
to that directory shown in Finder -
execute the following in the shell:
lldb -arch ARCH Products/Applications/APPNAME.app/APPNAME
-
inside lldb do the following:
(lldb) add-dsym dSYMs/APPNAME.app.dSYM/Contents/Resources/DWARF/APPNAME (lldb) disassemble --name CRASHING_FUNCTION_NAME
-
you now see a rich disassembly with symbols, and lo and behold, each line shows the same decimal offset as the original useless Apple backtrace (in the OPs example, the useless offset was
47
), as in:APPNAME[0xf4a7c] <+47>: ldr r0, [r0, r1]
you might be able to figure out the corresponding source line just from this information, if the disassembly has enough symbols to help you figure out where you are.
-
if not, there is another great trick. Pass the address of the line that crashed:
(lldb) image lookup -v --address 0xf4a7c
-
Now
lldb
shows you a rich collection of information---much richer than what is shown by Apple stack backtraces even when they do contain line numbers, and much richer than lldbsource list
---about all the source lines that contributed to the assembler instruction at that address. Pay close attention to both theSummary
andLineEntry
sections. Example:Address: APPNAME[0x000f4a7c] (APPNAME.__TEXT.__text + 963740) Summary: APPNAME`myclass::myfunc(bool, bool) + 904 [inlined] std::__1::deque<mystruct, std::__1::allocator<mystruct> >::operator[](unsigned long) + 22 at myfile.cpp:37945 APPNAME`myclass::myfunc(bool, bool) + 882 [inlined] myinlinefunc(int) + 14 at myfile.cpp:65498 APPNAME`myclass::myfunc(bool, bool) + 868 at myfile.cpp:65498 Module: file = "/Users/myuser/mydir/arch/Products/Applications/APPNAME.app/APPNAME", arch = "armv7" CompileUnit: id = {0x000483a4}, file = "/Users/myuser/mydir/myfile.cpp", language = "objective-c++" Function: id = {0x0045edde}, name = "myfunc", range = [0x000f46f4-0x000f572a) FuncType: id = {0x0045edde}, decl = myfile.cpp:65291, compiler_type = "void (_Bool, _Bool)" Blocks: id = {0x0045edde}, range = [0x000f46f4-0x000f572a) id = {0x0045f7d8}, ranges = [0x000f4936-0x000f51c0)[0x000f544c-0x000f5566)[0x000f5570-0x000f5698) id = {0x0046044c}, ranges = [0x000f49c6-0x000f49ce)[0x000f49d6-0x000f49d8)[0x000f4a2e-0x000f4a38)[0x000f4a58-0x000f4a82), name = "myinlinefunc", decl = myfile.cpp:37938, mangled = _Z11myinlinefunci, demangled = myinlinefunc(int) id = {0x00460460}, ranges = [0x000f4a58-0x000f4a64)[0x000f4a66-0x000f4a82), name = "operator[]", decl = deque:1675, mangled = _ZNSt3__15dequeI12mystructNS_9allocatorIS1_EEEixEm, demangled = std::__1::deque<mystruct, std::__1::allocator<mystruct> >::operator[](unsigned long) LineEntry: [0x000f4a7c-0x000f4a82): /Applications/Xcode7.3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/deque:1678:14 Symbol: id = {0x00000805}, range = [0x000f46f4-0x000f572a), name="myclass::myfunc(bool, bool)", mangled="_ZN7myclass7myfuncEbb" Variable: id = {0x00460459}, name = "myvar1", type = "int", location = , decl = myfile.cpp:37938 Variable: id = {0x0045f7dd}, name = "myvar2", type = "bool", location = , decl = myfile.cpp:65583 Variable: id = {0x0045edf2}, name = "this", type = "myclass *", location = [sp+56], decl = Variable: id = {0x0045ee01}, name = "myvar3", type = "bool", location = , decl = myfile.cpp:65291 Variable: id = {0x0045ee0e}, name = "myvar4", type = "bool", location = , decl = myfile.cpp:65292
In this example under
Summary
, we can see that the line that crashed was actually a combination of code frommyclass::myfunc()
,myinlinefunc()
andstd::deque::operator[]
. This kind of mashing together is very common for optimized code. This is often enough information to find the offending source line of your code. UnderLineEntry
we see the line number for the most-nested code contributing to that assembler line, which in this case is in the STLstd::deque
code, but in other cases might be the exact line number you want in your code.Now the only remaining question is: why on earth doesn't Apple just do this for us in the original backtrace? They clearly have all this information themselves! Why do they make us jump through such hoops? What are they hiding?
Solution 3:
Your steps (image lookup
+ p/x addr + offset
) will give you the raw address, as you found. But the original crash report probably included an address before the method + offset --- it is just as easy to slide your binary to the correct address using target modules load
. At the end of the crash report there should be a list of the binary images present in the program, including load address and UUID.
But more importantly, while the address is nice what you're really after is the source location. In that case, once you've determined the correct address for the method (or slid it to the matching address via target modules load
), you can use source list
(lldb) so l -a `addr + offset`
I'm using the backtick notation here which does an in-line expression evaluation. There's a handy shortcut for most commands that take an address: if you omit spaces, you can write the expression without backticks:
(lldb) so l -a addr+offset
You can also use image lookup
with an address. If you have debug information, this will tell you what the current location of variables are at this point. Why is this useful? Because most crash reports include the register context at crash and so any variables that are currently in a register are provided to you (-v
is necessary to get all of the register location information).
(lldb) im loo -v -a addr+offset
Finally -- this isn't going to work because you're dealing with an Objective-C method name -- but with a simple C function name you can do the offset arithmetic in-line as long as you cast the function name to a pointer type (it's not legal C to add an offset to a function pointer). e.g.
(lldb) so l -a (char*)main+10