summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibCoredump/Backtrace.cpp
blob: 86b53584cbd47542325c4f6d7034d5b36ad90f14 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
 * Copyright (c) 2020, Linus Groh <linusg@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/LexicalPath.h>
#include <AK/MappedFile.h>
#include <AK/Platform.h>
#include <AK/StringBuilder.h>
#include <AK/Types.h>
#include <LibCore/File.h>
#include <LibCoredump/Backtrace.h>
#include <LibCoredump/Reader.h>
#include <LibELF/Core.h>
#include <LibELF/Image.h>

namespace Coredump {

ELFObjectInfo const* Backtrace::object_info_for_region(ELF::Core::MemoryRegionInfo const& region)
{
    auto path = region.object_name();
    if (!path.starts_with('/') && (path.ends_with(".so"sv) || path.contains(".so."sv)))
        path = LexicalPath::join("/usr/lib", path).string();

    auto maybe_ptr = m_debug_info_cache.get(path);
    if (maybe_ptr.has_value())
        return *maybe_ptr;

    if (!Core::File::exists(path))
        return nullptr;

    auto file_or_error = MappedFile::map(path);
    if (file_or_error.is_error())
        return nullptr;

    auto image = make<ELF::Image>(file_or_error.value()->bytes());
    auto& image_reference = *image;
    auto info = make<ELFObjectInfo>(file_or_error.release_value(), make<Debug::DebugInfo>(image_reference), move(image));
    auto* info_ptr = info.ptr();
    m_debug_info_cache.set(path, move(info));
    return info_ptr;
}

Backtrace::Backtrace(const Reader& coredump, const ELF::Core::ThreadInfo& thread_info, Function<void(size_t, size_t)> on_progress)
    : m_thread_info(move(thread_info))
{
#if ARCH(I386)
    auto* start_bp = (FlatPtr*)m_thread_info.regs.ebp;
    auto* start_ip = (FlatPtr*)m_thread_info.regs.eip;
#else
    auto* start_bp = (FlatPtr*)m_thread_info.regs.rbp;
    auto* start_ip = (FlatPtr*)m_thread_info.regs.rip;
#endif

    // In order to provide progress updates, we first have to walk the
    // call stack to determine how many frames it has.
    size_t frame_count = 0;
    {
        auto* bp = start_bp;
        auto* ip = start_ip;
        while (bp && ip) {
            ++frame_count;
            auto next_ip = coredump.peek_memory((FlatPtr)(bp + 1));
            auto next_bp = coredump.peek_memory((FlatPtr)(bp));
            if (!next_ip.has_value() || !next_bp.has_value())
                break;
            ip = (FlatPtr*)next_ip.value();
            bp = (FlatPtr*)next_bp.value();
        }
    }

    auto* bp = start_bp;
    auto* ip = start_ip;
    size_t frame_index = 0;
    while (bp && ip) {
        // We use eip - 1 because the return address from a function frame
        // is the instruction that comes after the 'call' instruction.
        // However, because the first frame represents the faulting
        // instruction rather than the return address we don't subtract
        // 1 there.
        VERIFY((FlatPtr)ip > 0);
        add_entry(coredump, (FlatPtr)ip - ((frame_index == 0) ? 0 : 1));
        if (on_progress)
            on_progress(frame_index, frame_count);
        ++frame_index;
        auto next_ip = coredump.peek_memory((FlatPtr)(bp + 1));
        auto next_bp = coredump.peek_memory((FlatPtr)(bp));
        if (!next_ip.has_value() || !next_bp.has_value())
            break;
        ip = (FlatPtr*)next_ip.value();
        bp = (FlatPtr*)next_bp.value();
    }
}

Backtrace::~Backtrace()
{
}

void Backtrace::add_entry(const Reader& coredump, FlatPtr ip)
{
    auto* ip_region = coredump.region_containing((FlatPtr)ip);
    if (!ip_region) {
        m_entries.append({ ip, {}, {}, {} });
        return;
    }
    auto object_name = ip_region->object_name();
    // Only skip addresses coming from Loader.so if the faulting instruction is not in Loader.so
    if (object_name == "Loader.so") {
        if (m_skip_loader_so)
            return;
    } else {
        m_skip_loader_so = true;
    }
    // We need to find the first region for the object, just in case
    // the PT_LOAD header for the .text segment isn't the first one
    // in the object file.
    auto region = coredump.first_region_for_object(object_name);
    auto* object_info = object_info_for_region(*region);
    if (!object_info)
        return;

    auto function_name = object_info->debug_info->elf().symbolicate(ip - region->region_start);
    auto source_position = object_info->debug_info->get_source_position_with_inlines(ip - region->region_start);
    m_entries.append({ ip, object_name, function_name, source_position });
}

String Backtrace::Entry::to_string(bool color) const
{
    StringBuilder builder;
    builder.appendff("{:p}: ", eip);
    if (object_name.is_empty()) {
        builder.append("???");
        return builder.build();
    }
    builder.appendff("[{}] {}", object_name, function_name.is_empty() ? "???" : function_name);
    builder.append(" (");

    Vector<Debug::DebugInfo::SourcePosition> source_positions;

    for (auto& position : source_position_with_inlines.inline_chain) {
        if (!source_positions.contains_slow(position))
            source_positions.append(position);
    }

    if (source_position_with_inlines.source_position.has_value() && !source_positions.contains_slow(source_position_with_inlines.source_position.value())) {
        source_positions.insert(0, source_position_with_inlines.source_position.value());
    }

    for (size_t i = 0; i < source_positions.size(); ++i) {
        auto& position = source_positions[i];
        auto fmt = color ? "\033[34;1m{}\033[0m:{}" : "{}:{}";
        builder.appendff(fmt, LexicalPath::basename(position.file_path), position.line_number);
        if (i != source_positions.size() - 1) {
            builder.append(" => ");
        }
    }

    builder.append(")");

    return builder.build();
}

}