source: XIOS3/trunk/extern/cpptrace/src/symbols_with_addr2line.cpp @ 2573

Last change on this file since 2573 was 2573, checked in by ymipsl, 9 months ago

create new external source lib : cpptrace, for statck trace output
YM

File size: 16.1 KB
Line 
1#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
2
3#include "cpptrace.hpp"
4#include "symbols.hpp"
5#include "common.hpp"
6
7#include <cstdint>
8#include <cstdio>
9#include <functional>
10#include <mutex>
11#include <string>
12#include <unordered_map>
13#include <utility>
14#include <vector>
15
16#if IS_LINUX || IS_APPLE
17 #include <unistd.h>
18 // NOLINTNEXTLINE(misc-include-cleaner)
19 #include <sys/types.h>
20 #include <sys/wait.h>
21#endif
22
23#include "object.hpp"
24
25namespace cpptrace {
26    namespace detail {
27        namespace addr2line {
28            #if IS_LINUX || IS_APPLE
29            bool has_addr2line() {
30                static std::mutex mutex;
31                static bool has_addr2line = false;
32                static bool checked = false;
33                std::lock_guard<std::mutex> lock(mutex);
34                if(!checked) {
35                    checked = true;
36                    // Detects if addr2line exists by trying to invoke addr2line --help
37                    constexpr int magic = 42;
38                    // NOLINTNEXTLINE(misc-include-cleaner)
39                    const pid_t pid = fork();
40                    if(pid == -1) { return false; }
41                    if(pid == 0) { // child
42                        close(STDOUT_FILENO);
43                        close(STDERR_FILENO); // atos --help writes to stderr
44                        #ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
45                        #if !IS_APPLE
46                        execlp("addr2line", "addr2line", "--help", nullptr);
47                        #else
48                        execlp("atos", "atos", "--help", nullptr);
49                        #endif
50                        #else
51                        #ifndef CPPTRACE_ADDR2LINE_PATH
52                        #error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
53                        #endif
54                        execl(CPPTRACE_ADDR2LINE_PATH, CPPTRACE_ADDR2LINE_PATH, "--help", nullptr);
55                        #endif
56                        _exit(magic);
57                    }
58                    int status;
59                    waitpid(pid, &status, 0);
60                    // NOLINTNEXTLINE(misc-include-cleaner)
61                    has_addr2line = WEXITSTATUS(status) == 0;
62                }
63                return has_addr2line;
64            }
65
66            struct pipe_t {
67                union {
68                    struct {
69                        int read_end;
70                        int write_end;
71                    };
72                    int data[2];
73                };
74            };
75            static_assert(sizeof(pipe_t) == 2 * sizeof(int), "Unexpected struct packing");
76
77            std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
78                pipe_t output_pipe;
79                pipe_t input_pipe;
80                internal_verify(pipe(output_pipe.data) == 0);
81                internal_verify(pipe(input_pipe.data) == 0);
82                // NOLINTNEXTLINE(misc-include-cleaner)
83                const pid_t pid = fork();
84                if(pid == -1) { return ""; } // error? TODO: Diagnostic
85                if(pid == 0) { // child
86                    dup2(output_pipe.write_end, STDOUT_FILENO);
87                    dup2(input_pipe.read_end, STDIN_FILENO);
88                    close(output_pipe.read_end);
89                    close(output_pipe.write_end);
90                    close(input_pipe.read_end);
91                    close(input_pipe.write_end);
92                    close(STDERR_FILENO); // TODO: Might be worth conditionally enabling or piping
93                    #ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
94                    #if !IS_APPLE
95                    execlp("addr2line", "addr2line", "-e", executable.c_str(), "-f", "-C", "-p", nullptr);
96                    #else
97                    execlp("atos", "atos", "-o", executable.c_str(), "-fullPath", nullptr);
98                    #endif
99                    #else
100                    #ifndef CPPTRACE_ADDR2LINE_PATH
101                    #error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
102                    #endif
103                    #if !IS_APPLE
104                    execl(
105                        CPPTRACE_ADDR2LINE_PATH,
106                        CPPTRACE_ADDR2LINE_PATH,
107                        "-e",
108                        executable.c_str(),
109                        "-f",
110                        "-C",
111                        "-p",
112                        nullptr
113                    );
114                    #else
115                    execl(
116                        CPPTRACE_ADDR2LINE_PATH,
117                        CPPTRACE_ADDR2LINE_PATH,
118                        "-o", executable.c_str(),
119                        "-fullPath",
120                        nullptr
121                    );
122                    #endif
123                    #endif
124                    _exit(1); // TODO: Diagnostic?
125                }
126                internal_verify(write(input_pipe.write_end, addresses.data(), addresses.size()) != -1);
127                close(input_pipe.read_end);
128                close(input_pipe.write_end);
129                close(output_pipe.write_end);
130                std::string output;
131                constexpr int buffer_size = 4096;
132                char buffer[buffer_size];
133                size_t count = 0;
134                while((count = read(output_pipe.read_end, buffer, buffer_size)) > 0) {
135                    output.insert(output.end(), buffer, buffer + count);
136                }
137                // TODO: check status from addr2line?
138                waitpid(pid, nullptr, 0);
139                return output;
140            }
141            #elif IS_WINDOWS
142            bool has_addr2line() {
143                static std::mutex mutex;
144                static bool has_addr2line = false;
145                static bool checked = false;
146                std::lock_guard<std::mutex> lock(mutex);
147                if(!checked) {
148                    // TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
149                    checked = true;
150                    #ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
151                    FILE* p = popen("addr2line --version", "r");
152                    #else
153                    #ifndef CPPTRACE_ADDR2LINE_PATH
154                    #error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
155                    #endif
156                    FILE* p = popen(CPPTRACE_ADDR2LINE_PATH " --version", "r");
157                    #endif
158                    if(p) {
159                        has_addr2line = pclose(p) == 0;
160                    }
161                }
162                return has_addr2line;
163            }
164
165            std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
166                // TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
167                ///fprintf(stderr, ("addr2line -e " + executable + " -fCp " + addresses + "\n").c_str());
168                #ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
169                FILE* p = popen(("addr2line -e \"" + executable + "\" -fCp " + addresses).c_str(), "r");
170                #else
171                #ifndef CPPTRACE_ADDR2LINE_PATH
172                #error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
173                #endif
174                FILE* p = popen(
175                    (CPPTRACE_ADDR2LINE_PATH " -e \"" + executable + "\" -fCp " + addresses).c_str(),
176                    "r"
177                );
178                #endif
179                std::string output;
180                constexpr int buffer_size = 4096;
181                char buffer[buffer_size];
182                size_t count = 0;
183                while((count = fread(buffer, 1, buffer_size, p)) > 0) {
184                    output.insert(output.end(), buffer, buffer + count);
185                }
186                pclose(p);
187                ///fprintf(stderr, "%s\n", output.c_str());
188                return output;
189            }
190            #endif
191
192            using target_vec = std::vector<std::pair<std::string, std::reference_wrapper<stacktrace_frame>>>;
193
194            // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
195            std::unordered_map<std::string, target_vec> get_addr2line_targets(
196                const std::vector<dlframe>& dlframes,
197                std::vector<stacktrace_frame>& trace
198            ) {
199                std::unordered_map<std::string, target_vec> entries;
200                for(std::size_t i = 0; i < dlframes.size(); i++) {
201                    const auto& entry = dlframes[i];
202                    // If libdl fails to find the shared object for a frame, the path will be empty. I've observed this
203                    // on macos when looking up the shared object containing `start`.
204                    if(!entry.obj_path.empty()) {
205                        ///fprintf(
206                        ///    stderr,
207                        ///    "%s %s\n",
208                        ///    to_hex(entry.raw_address).c_str(),
209                        ///    to_hex(entry.raw_address - entry.obj_base + base).c_str()
210                        ///);
211                        try {
212                            entries[entry.obj_path].emplace_back(
213                                to_hex(entry.obj_address),
214                                trace[i]
215                            );
216                        } catch(file_error&) {
217                            //
218                        } catch(...) {
219                            throw;
220                        }
221                        // Set what is known for now, and resolutions from addr2line should overwrite
222                        trace[i].filename = entry.obj_path;
223                        trace[i].symbol = entry.symbol;
224                    }
225                }
226                return entries;
227            }
228
229            // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
230            void update_trace(const std::string& line, size_t entry_index, const target_vec& entries_vec) {
231                #if !IS_APPLE
232                // Result will be of the form "<symbol> at path:line"
233                // The path may be ?? if addr2line cannot resolve, line may be ?
234                // Edge cases:
235                // ?? ??:0
236                // symbol :?
237                const std::size_t at_location = line.find(" at ");
238                std::size_t symbol_end;
239                std::size_t filename_start;
240                if(at_location != std::string::npos) {
241                    symbol_end = at_location;
242                    filename_start = at_location + 4;
243                } else {
244                    internal_verify(line.find("?? ") == 0, "Unexpected edge case while processing addr2line output");
245                    symbol_end = 2;
246                    filename_start = 3;
247                }
248                auto symbol = line.substr(0, symbol_end);
249                auto colon = line.rfind(':');
250                internal_verify(colon != std::string::npos);
251                internal_verify(colon >= filename_start); // :? to deal with "symbol :?" edge case
252                auto filename = line.substr(filename_start, colon - filename_start);
253                auto line_number = line.substr(colon + 1);
254                if(line_number != "?") {
255                    entries_vec[entry_index].second.get().line = std::stoi(line_number);
256                }
257                if(!filename.empty() && filename != "??") {
258                    entries_vec[entry_index].second.get().filename = filename;
259                }
260                if(!symbol.empty()) {
261                    entries_vec[entry_index].second.get().symbol = symbol;
262                }
263                #else
264                // Result will be of the form "<symbol> (in <object name>) (file:line)"
265                // The symbol may just be the given address if atos can't resolve it
266                // Examples:
267                // trace() (in demo) (demo.cpp:8)
268                // 0x100003b70 (in demo)
269                // 0xffffffffffffffff
270                // foo (in bar) + 14
271                // I'm making some assumptions here. Support may need to be improved later. This is tricky output to
272                // parse.
273                const std::size_t in_location = line.find(" (in ");
274                if(in_location == std::string::npos) {
275                    // presumably the 0xffffffffffffffff case
276                    return;
277                }
278                const std::size_t symbol_end = in_location;
279                entries_vec[entry_index].second.get().symbol = line.substr(0, symbol_end);
280                const std::size_t obj_end = line.find(")", in_location);
281                internal_verify(
282                    obj_end != std::string::npos,
283                    "Unexpected edge case while processing addr2line/atos output"
284                );
285                const std::size_t filename_start = line.find(") (", obj_end);
286                if(filename_start == std::string::npos) {
287                    // presumably something like 0x100003b70 (in demo) or foo (in bar) + 14
288                    return;
289                }
290                const std::size_t filename_end = line.find(":", filename_start);
291                internal_verify(
292                    filename_end != std::string::npos,
293                    "Unexpected edge case while processing addr2line/atos output"
294                );
295                entries_vec[entry_index].second.get().filename = line.substr(
296                    filename_start + 3,
297                    filename_end - filename_start - 3
298                );
299                const std::size_t line_start = filename_end + 1;
300                const std::size_t line_end = line.find(")", filename_end);
301                internal_verify(
302                    line_end == line.size() - 1,
303                    "Unexpected edge case while processing addr2line/atos output"
304                );
305                entries_vec[entry_index].second.get().line = std::stoi(line.substr(line_start, line_end - line_start));
306                #endif
307            }
308
309            // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
310            std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
311                // TODO: Refactor better
312                std::vector<stacktrace_frame> trace(frames.size(), stacktrace_frame { 0, 0, 0, "", "" });
313                for(size_t i = 0; i < frames.size(); i++) {
314                    trace[i].address = reinterpret_cast<uintptr_t>(frames[i]);
315                }
316                if(has_addr2line()) {
317                    const std::vector<dlframe> dlframes = get_frames_object_info(frames);
318                    const auto entries = get_addr2line_targets(dlframes, trace);
319                    for(const auto& entry : entries) {
320                        const auto& object_name = entry.first;
321                        const auto& entries_vec = entry.second;
322                        // You may ask why it'd ever happen that there could be an empty entries_vec array, if there're
323                        // no addresses why would get_addr2line_targets do anything? The reason is because if things in
324                        // get_addr2line_targets fail it will silently skip. This is partly an optimization but also an
325                        // assertion below will fail if addr2line is given an empty input.
326                        if(entries_vec.empty()) {
327                            continue;
328                        }
329                        std::string address_input;
330                        for(const auto& pair : entries_vec) {
331                            address_input += pair.first;
332                            #if !IS_WINDOWS
333                                address_input += '\n';
334                            #else
335                                address_input += ' ';
336                            #endif
337                        }
338                        auto output = split(trim(resolve_addresses(address_input, object_name)), "\n");
339                        internal_verify(output.size() == entries_vec.size());
340                        for(size_t i = 0; i < output.size(); i++) {
341                            update_trace(output[i], i, entries_vec);
342                        }
343                    }
344                }
345                return trace;
346            }
347        }
348    }
349}
350
351#endif
Note: See TracBrowser for help on using the repository browser.