TLDR: Proposing to extend gSYM format with call site information for each function in order to help merged function disambiguation.
Context:
In a previous RFC and PR we added support for storing merged function information in gSYM. Now we need a way to choose which function to show when looking at a call stack. This proposal is about adding the extra information we need to do this.
Problems We’re Trying to Solve
Here’s an example call stack:
Frame0: Addr00 ....
Frame1: Addr01 ....
Frame2: Addr02 ....
Frame3: Addr03 ....
Frame4: Addr04 [Merged01,Merged02,Merged03]
We need to figure out which of Merged01
, Merged02
, or Merged03
to show for Frame4. The easiest way is to look at who called this function. Using the return address pointing into Frame3::Addr03
we can check which function is expected to be called from within Frame3
just before Addr03
and select [Merged01,Merged02,Merged03] accordingly.
Scenarios we need to consider in this design:
- Simple case:
Frame3:Addr03
is in the same binary asFrame4
and it directly calls it. This is the basic case and this doesn’t require any advanced logic to figure out which merged function to select -Frame3:Addr03
will directly specify that it’s callingFrame4
- Calls across dylibs: It may be the case that
Frame4
is in one dyblib andFrame3
is in another dylib. This complicates the scenario because the information forFrame3
andFrame4
will be in separate gSYM files - so we have to have a way of resolving merged functions where the information about what function is called and the information about the merged functions are present in separate gSYMs. - Calls through system libraries: It may be the case that
Frame04
is called through a system library (ex:dispatch_client_callout
orenumerate_objects_with_options
) so the information in the immediate frame is not helpful and we would need to look further up the stack. Ex:Frame02
is the actual caller, thenFrame03
isdispatch_client_callout
thenFrame04
is the called function. - Virtual function calls: In this case the target could be a list of known functions, or we might know the function name but not the class name.
- ObjC selector calls: Similar to #4 above, where we would know the selector name but not the class name.
Proposed solution
We want to change the gSYM format like this:
For each function, add a list of all the calls it makes. For each call, we’ll store:
- Where the function returns to
- Some extra flags (like if it calls a different part of the program)
- A pattern to match the function name (RegEx)
The above information should give us all the information we need in order to successfully select the appropriate merged function information. When walking the callstack, at each point we can retrieve information about the expected name of the next call (via a regex). We can pass this regex to the resolution of the following frame - so if there are a list of merged functions for the next frame, the regex can be used to match the correct function name.
If the target function is known precisely and the target function is in the same gSYM, we can simply reuse the already present function name in the string table, to not use additional storage for a new string.
Backward compatibility
This will be additional information for a FunctionInfo - for older version clients that do not support this information, they can just ignore it.
Input data
We can input data from multiple sources. DWARF-5 has support for callsites so we can get callsite information from there. However, it only supports callsite information for direct calls. We can gather additional callsite information from the compiler for more complex scenarios like virtual functions, calls through system libraries or ObjC selector invocations).
For the alternate input methods, as well as for testing reasons we can have the callsite information also be loaded from a .json
(or text) file - passed to the llvm-gsymutil
via the command line. This will provide the callsites in text format, specifying the callsite return address and the rest of the information mentioned above.