This is an archive of the discontinued LLVM Phabricator instance.

[lldb][trace] Add a basic function call dump [2] - Implement the reconstruction algorithm
ClosedPublic

Authored by wallace on Oct 13 2022, 2:15 PM.

Details

Summary

This diff implements the reconstruction algorithm for the call tree and
add tests.

See TraceDumper.h for documentation and explanations.

One important detail is that the tree objects are in TraceDumper, even
though Trace.h is a better home. I'm leaving that as future work.

Another detail is that this code is as slow as dumping the entire
symolicated trace, which is not that bad tbh. The reason is that we use
symbols throughout the algorithm and we are not being careful about
memory and speed. This is also another area for future improvement.

Lastly, I made sure that incomplete traces work, i.e. you start tracing
very deep in the stack or failures randomly appear in the trace. Besides
that, inline functions are recognized and the linker is identified correctly.

For example, tracing the following code generates the following call tree

#include <iostream>
using namespace std;


__attribute__((always_inline)) inline int mult(int x, int y) {
  int f = x * y;
  f++;
  f *= f;
  return f;
}

int foo(int x) {
  int z = mult(x, x - 1);
  z++;
  return z;
}

int main() {
  int x = 12;
  int z = foo(x);
  cout << z << endl;
}
(lldb) thread trace dump function-calls      

thread #1: tid = 1187473

[call tree #0]
inline`main + 8 at inline.cpp:19:7 to 20:14  [1, 5]
  inline`foo(int) at inline.cpp:12:16 to 13:15  [6, 13]
    inline`foo(int) + 22 [inlined] mult(int, int) at inline.cpp:6:7 to 9:10  [14, 21]
  inline`foo(int) + 49 at inline.cpp:13:15 to 16:1  [22, 26]
inline`main + 25 at inline.cpp:20:14 to 21:11  [27, 31]
  inline`symbol stub for: std::ostream::operator<<(int) to <+11>  [32, 34]
    inline`inline[0x00000000004006b0] to inline[0x00000000004006b6]  [35, 36]
      ld-2.28.so`_dl_runtime_resolve_xsavec to 126:0  [37, 60]
        ld-2.28.so`.annobin__dl_relocate_object.end to 94:16  [61, 115]
          ld-2.28.so`_dl_lookup_symbol_x to 855:9  [116, 245]
            ld-2.28.so`do_lookup_x to 430:10  [246, 422]
              ld-2.28.so`.annobin_dl_lookup.c to 88:21  [423, 451]
                ld-2.28.so`strcmp to 2227:0  [452, 476]
              ld-2.28.so`.annobin_dl_lookup.c + 109 to 116:12  [477, 496]
                ld-2.28.so`strcmp to 2227:0  [497, 521]
              ld-2.28.so`.annobin_dl_lookup.c + 316 to 161:1  [522, 530]
            ld-2.28.so`do_lookup_x + 910 to 569:1  [531, 565]
          ld-2.28.so`_dl_lookup_symbol_x + 289 to 948:1  [566, 600]
        ld-2.28.so`.annobin__dl_relocate_object.end + 216 to 162:1  [601, 635]
      ld-2.28.so`_dl_runtime_resolve_xsavec + 126 to 153:0  [636, 650]
        libstdc++.so.6`std::ostream::operator<<(int) to <+31>  [652, 662]
          libstdc++.so.6` + 3520 to <+3524>  [663, 664]
            libstdc++.so.6`std::ostream& std::ostream::_M_insert<long>(long) to <+49>  [665, 681]
              libstdc++.so.6` + 13072 to <+13076>  [682, 683]
                libstdc++.so.6`std::ostream::sentry::sentry(std::ostream&) to <+112>  [684, 706]
            libstdc++.so.6`std::ostream& std::ostream::_M_insert<long>(long) + 54 to <+275>  [707, 723]
              libstdc++.so.6` + 5920 to <+5924>  [724, 725]
                libstdc++.so.6`std::ctype<char>::_M_widen_init() const to <+319>  [726, 1802]
                  libstdc++.so.6` + 10288 to <+10292>  [1803, 1804]
                    libc.so.6`__memcmp_avx2_movbe to <+212>  [1805, 1850]
                libstdc++.so.6`std::ctype<char>::_M_widen_init() const + 324 to <+362>  [1851, 1860]
            libstdc++.so.6`std::ostream& std::ostream::_M_insert<long>(long) + 280 to <+134>  [1861, 1881]
              libstdc++.so.6`std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char>>>::do_put(std::ostreambuf_iterator<char, std::char_traits<char>>, std::ios_base&, char, long) const to <+8>  [1882, 1884]
                libstdc++.so.6` + 8336 to <+8340>  [1885, 1886]
                  libstdc++.so.6`std::ostreambuf_iterator<char, std::char_traits<char>> std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char>>>::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char>>, std::ios_base&, char, long) const to <+67>  [1887, 1907]
                    libstdc++.so.6`std::__use_cache<std::__numpunct_cache<char>>::operator()(std::locale const&) const to <+24>  [1908, 1916]
                      libstdc++.so.6` + 4192 to <+4196>  [1917, 1918]
                        libstdc++.so.6`std::locale::id::_M_id() const to <+50>  [1919, 1924]
                    libstdc++.so.6`std::__use_cache<std::__numpunct_cache<char>>::operator()(std::locale const&) const + 29 to <+63>  [1925, 1937]
                  libstdc++.so.6`std::ostreambuf_iterator<char, std::char_traits<char>> std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char>>>::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char>>, std::ios_base&, char, long) const + 72 to <+200>  [1938, 1966]
                    libstdc++.so.6`int std::__int_to_char<char, unsigned long>(char*, unsigned long, char const*, std::_Ios_Fmtflags, bool) to <+83>  [1967, 2040]
                  libstdc++.so.6`std::ostreambuf_iterator<char, std::char_traits<char>> std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char>>>::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char>>, std::ios_base&, char, long) const + 205 to <+325>  [2041, 2068]
                    libstdc++.so.6`__gnu_cxx::stdio_sync_filebuf<char, std::char_traits<char>>::xsputn(char const*, long) to <+19>  [2069, 2074]
                      libstdc++.so.6` + 11600 to <+11604>  [2075, 2076]
                        libc.so.6`_IO_fwrite to <+216>  [2078, 2126]
                          libc.so.6`_IO_file_xsputn@@GLIBC_2.2.5 to <+266>  [2127, 2160]
                            libc.so.6`_IO_file_overflow@@GLIBC_2.2.5 to <+403>  [2161, 2176]
                              libc.so.6`_IO_doallocbuf to <+77>  [2177, 2196]
                                libc.so.6`_IO_file_doallocate to <+93>  [2197, 2219]
                                  libc.so.6`_IO_file_stat to <+15>  [2220, 2224]
                                    libc.so.6`__fxstat64 to <+31>  [2226, 2237]
                                libc.so.6`_IO_file_doallocate + 99 to <+139>  [2238, 2261]
                                  libc.so.6` + 160 to <+164>  [2262, 2263]
                                    libc.so.6`malloc to <+137>  [2264, 2302]
                                      libc.so.6`_int_malloc to <+1254>  [2303, 2466]
                                    libc.so.6`malloc + 142 to <+199>  [2467, 2479]
                                libc.so.6`_IO_file_doallocate + 144 to <+169>  [2480, 2487]
                                  libc.so.6`_IO_setb to <+55>  [2488, 2506]
                                libc.so.6`_IO_file_doallocate + 174 to <+213>  [2507, 2515]
                              libc.so.6`_IO_doallocbuf + 80 to <+131>  [2516, 2521]
                            libc.so.6`_IO_file_overflow@@GLIBC_2.2.5 + 408 to <+231>  [2522, 2556]
                              libc.so.6`_IO_do_write@@GLIBC_2.2.5 to <+11>  [2557, 2561]
                          libc.so.6`_IO_file_xsputn@@GLIBC_2.2.5 + 271 to <+332>  [2562, 2581]
                            libc.so.6`_IO_default_xsputn to <+110>  [2582, 2615]
                              libc.so.6`_IO_file_overflow@@GLIBC_2.2.5 to <+108>  [2616, 2649]
                            libc.so.6`_IO_default_xsputn + 113 to <+110>  [2650, 2668]
                              libc.so.6`_IO_file_overflow@@GLIBC_2.2.5 to <+108>  [2669, 2702]
                            libc.so.6`_IO_default_xsputn + 113 to <+110>  [2703, 2721]
                              libc.so.6`_IO_file_overflow@@GLIBC_2.2.5 to <+108>  [2722, 2755]
                            libc.so.6`_IO_default_xsputn + 113 to <+110>  [2756, 2774]
                              libc.so.6`_IO_file_overflow@@GLIBC_2.2.5 to <+108>  [2775, 2808]
                            libc.so.6`_IO_default_xsputn + 113 to <+110>  [2809, 2827]
                              libc.so.6`_IO_file_overflow@@GLIBC_2.2.5 to <+108>  [2828, 2861]
                            libc.so.6`_IO_default_xsputn + 113 to <+198>  [2862, 2880]
                          libc.so.6`_IO_file_xsputn@@GLIBC_2.2.5 + 337 to <+143>  [2881, 2892]
                        libc.so.6`_IO_fwrite + 220 to <+332>  [2893, 2917]
                  libstdc++.so.6`std::ostreambuf_iterator<char, std::char_traits<char>> std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char>>>::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char>>, std::ios_base&, char, long) const + 328 to <+379>  [2918, 2934]
            libstdc++.so.6`std::ostream& std::ostream::_M_insert<long>(long) + 137 to <+165>  [2935, 2938]
              libstdc++.so.6` + 3056 to <+3060>  [2939, 2940]
                libstdc++.so.6`std::ostream::sentry::~sentry() to <+80>  [2941, 2947]
            libstdc++.so.6`std::ostream& std::ostream::_M_insert<long>(long) + 170 to <+207>  [2948, 2959]
inline`main + 43 at inline.cpp:21:16 to 21:16  [2960, 2962]
  inline`symbol stub for: std::ostream::operator<<(std::ostream& (*)(std::ostream&)) to <+11>  [2963, 2965]
    inline`inline[0x00000000004006b0] to inline[0x00000000004006b6]  [2966, 2967]
      ld-2.28.so`_dl_runtime_resolve_xsavec to 126:0  [2968, 2991]
        ld-2.28.so`.annobin__dl_relocate_object.end to 94:16  [2992, 3046]
          ld-2.28.so`_dl_lookup_symbol_x to 855:9  [3047, 3232]
            ld-2.28.so`do_lookup_x to 430:10  [3233, 3401]
              ld-2.28.so`.annobin_dl_lookup.c to 88:21  [3402, 3430]
                ld-2.28.so`strcmp to 2227:0  [3431, 3481]
              ld-2.28.so`.annobin_dl_lookup.c + 109 to 116:12  [3482, 3501]
                ld-2.28.so`strcmp to 2227:0  [3502, 3526]
              ld-2.28.so`.annobin_dl_lookup.c + 316 to 161:1  [3527, 3535]
            ld-2.28.so`do_lookup_x + 910 to 569:1  [3536, 3570]
          ld-2.28.so`_dl_lookup_symbol_x + 289 to 948:1  [3571, 3605]
        ld-2.28.so`.annobin__dl_relocate_object.end + 216 to 162:1  [3606, 3640]
      ld-2.28.so`_dl_runtime_resolve_xsavec + 126 to 153:0  [3641, 3655]
        libstdc++.so.6`std::ostream::operator<<(std::ostream& (*)(std::ostream&)) to <+4>  [3656, 3657]
          inline`symbol stub for: std::basic_ostream<char, std::char_traits<char>>& std::endl<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&) to <+11>  [3658, 3660]
            inline`inline[0x00000000004006b0] to inline[0x00000000004006b6]  [3661, 3662]
              ld-2.28.so`_dl_runtime_resolve_xsavec to 126:0  [3663, 3686]
                ld-2.28.so`.annobin__dl_relocate_object.end to 94:16  [3687, 3741]
                  ld-2.28.so`_dl_lookup_symbol_x to 855:9  [3742, 4263]
                    ld-2.28.so`do_lookup_x to 430:10  [4264, 4372]
                      ld-2.28.so`.annobin_dl_lookup.c to 161:1  [4373, 4395]
                    ld-2.28.so`do_lookup_x + 910 to 430:10  [4396, 4513]
                      ld-2.28.so`.annobin_dl_lookup.c to 88:21  [4514, 4542]
                        ld-2.28.so`strcmp to 2227:0  [4543, 4663]
                      ld-2.28.so`.annobin_dl_lookup.c + 109 to 116:12  [4664, 4683]
                        ld-2.28.so`strcmp to 2227:0  [4684, 4708]
                      ld-2.28.so`.annobin_dl_lookup.c + 316 to 161:1  [4709, 4717]
                    ld-2.28.so`do_lookup_x + 910 to 569:1  [4718, 4752]
                  ld-2.28.so`_dl_lookup_symbol_x + 289 to 948:1  [4753, 4787]
                ld-2.28.so`.annobin__dl_relocate_object.end + 216 to 162:1  [4788, 4822]
              ld-2.28.so`_dl_runtime_resolve_xsavec + 126 to 153:0  [4823, 4837]
                libstdc++.so.6`std::basic_ostream<char, std::char_traits<char>>& std::endl<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&) to <+46>  [4838, 4852]
                  libstdc++.so.6` + 2672 to <+2676>  [4853, 4854]
                    libstdc++.so.6`std::ostream::put(char) to <+43>  [4855, 4868]
                      libstdc++.so.6` + 13072 to <+13076>  [4869, 4870]
                        libstdc++.so.6`std::ostream::sentry::sentry(std::ostream&) to <+112>  [4871, 4893]
                    libstdc++.so.6`std::ostream::put(char) + 48 to <+135>  [4894, 4904]
                      libstdc++.so.6`__gnu_cxx::stdio_sync_filebuf<char, std::char_traits<char>>::overflow(int) to <+17>  [4905, 4911]
                        libstdc++.so.6` + 8208 to <+8212>  [4912, 4913]
                          libc.so.6`_IO_putc to <+278>  [4914, 4925]
                            libc.so.6`__overflow to <+73>  [4926, 4943]
                              libc.so.6`_IO_file_overflow@@GLIBC_2.2.5 to <+254>  [4944, 4977]
                                libc.so.6`_IO_do_write@@GLIBC_2.2.5 to <+20>  [4978, 4983]
                                  libc.so.6`new_do_write to <+90>  [4984, 5011]
                                    libc.so.6`_IO_file_write@@GLIBC_2.2.5 to <+40>  [5012, 5030]
                                      libc.so.6`write to <+32>  [5031, 5041]
                                    libc.so.6`_IO_file_write@@GLIBC_2.2.5 + 45 to <+141>  [5042, 5059]
                                  libc.so.6`new_do_write + 95 to <+178>  [5060, 5084]
                                libc.so.6`_IO_do_write@@GLIBC_2.2.5 + 25 to <+37>  [5085, 5090]
                              libc.so.6`_IO_file_overflow@@GLIBC_2.2.5 + 259 to <+108>  [5091, 5097]
                    libstdc++.so.6`std::ostream::put(char) + 138 to <+90>  [5098, 5101]
                      libstdc++.so.6` + 3056 to <+3060>  [5102, 5103]
                        libstdc++.so.6`std::ostream::sentry::~sentry() to <+80>  [5104, 5110]
                    libstdc++.so.6`std::ostream::put(char) + 95 to <+122>  [5111, 5119]
                libstdc++.so.6`std::basic_ostream<char, std::char_traits<char>>& std::endl<char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&) + 51 to <+60>  [5120, 5124]
                  libstdc++.so.6` + 7696 to <+7700>  [5125, 5126]
                    libstdc++.so.6`std::ostream::flush() to <+31>  [5127, 5136]
                      libstdc++.so.6`__gnu_cxx::stdio_sync_filebuf<char, std::char_traits<char>>::sync() to <+8>  [5137, 5139]
                        libstdc++.so.6` + 13344 to <+13348>  [5140, 5141]
                          libc.so.6`_IO_fflush to <+158>  [5142, 5173]
                            libc.so.6`_IO_file_sync@@GLIBC_2.2.5 to <+172>  [5174, 5191]
                          libc.so.6`_IO_fflush + 161 to <+249>  [5192, 5210]
                    libstdc++.so.6`std::ostream::flush() + 34 to <+43>  [5211, 5215]

Diff Detail

Event Timeline

wallace created this revision.Oct 13 2022, 2:15 PM
Herald added a project: Restricted Project. · View Herald TranscriptOct 13 2022, 2:15 PM
wallace requested review of this revision.Oct 13 2022, 2:15 PM
Herald added a project: Restricted Project. · View Herald TranscriptOct 13 2022, 2:15 PM
wallace edited the summary of this revision. (Show Details)Oct 13 2022, 2:17 PM
jj10306 requested changes to this revision.Oct 14 2022, 7:04 AM

this is awesome - the tests do a great job documenting the code and weird edge cases so thanks for that.
just some minor nits and questions, looks good overall

lldb/include/lldb/Target/TraceDumper.h
86

nit

213–214

how would you have two different symbol info for the first and last? is last referring to the symbol of the next function?

216

nit:is the Optional<UP> redundant? thoughts on just using the UP and mentioning nullptr indicates there is no nested call

lldb/source/Target/TraceDumper.cpp
92–94

are these functions transferring ownership of these block pointers (ie are you responsible for freeing this mem)?

585

"that happens after a return"
from looking at how this function's called from AppendInstructionToFunctionCallForest this appears to be the return instruction, not the instruction after a return. is this correct?

618–620

this would only be the case for well behaved CALL/RET, right? in the case of modified stack return or JMP, this wouldn't necessarily be true?

784–786

wouldn't this be a very common case where a thread is in the middle of executing a function, gets context switched out of CPU X, then after some time gets context switched into CPU Y and continues executing the function?

lldb/test/API/commands/trace/TestTraceDumpFunctionCalls.py
31–32

this is saying that events 4 - 19524 were all executed by foo() and there weren't any other function calls? am I understanding this output correctly?

This revision now requires changes to proceed.Oct 14 2022, 7:04 AM
wallace marked 8 inline comments as done.Oct 14 2022, 2:18 PM
wallace added inline comments.
lldb/include/lldb/Target/TraceDumper.h
213–214

the SymbolInfo class contains disassemblies of the instruction and source line information, so technically two different instructions will always give you two different SymbolInfo objects.

216

I'll turn these structs into classes with stricter safety

lldb/source/Target/TraceDumper.cpp
92–94

there's no ownership transfer. They just expose the underlying pointers.

585

now really. This function is called only from a switch that operates on the previous instruction kind, not the current instruction kind

618–620

it's still true if you pop frames out from the stack, but if you return using a jmp, then anything could happen.

784–786

yes, and we won't solve this for the time being. We can leave this for next year.

lldb/test/API/commands/trace/TestTraceDumpFunctionCalls.py
31–32

s/events/trace items

and yes. It was a hot loop in the same function

wallace updated this revision to Diff 467911.Oct 14 2022, 2:18 PM
wallace marked 7 inline comments as done.
  • address all comments
  • turn all the structs into classes with safe accessors
jj10306 accepted this revision.Oct 17 2022, 5:47 PM
This revision is now accepted and ready to land.Oct 17 2022, 5:47 PM