Serenity Operating System
1/*
2 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
4 * Copyright (c) 2022, the SerenityOS developers.
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 */
8
9#include "DisassemblyModel.h"
10#include "Gradient.h"
11#include "PercentageFormatting.h"
12#include "Profile.h"
13#include <LibCore/MappedFile.h>
14#include <LibDebug/DebugInfo.h>
15#include <LibELF/Image.h>
16#include <LibSymbolication/Symbolication.h>
17#include <LibX86/Disassembler.h>
18#include <LibX86/ELFSymbolProvider.h>
19#include <stdio.h>
20
21namespace Profiler {
22
23static Optional<MappedObject> s_kernel_binary;
24
25static ELF::Image* try_load_kernel_binary()
26{
27 if (s_kernel_binary.has_value())
28 return &s_kernel_binary->elf;
29 auto kernel_binary_or_error = Core::MappedFile::map("/boot/Kernel"sv);
30 if (!kernel_binary_or_error.is_error()) {
31 auto kernel_binary = kernel_binary_or_error.release_value();
32 s_kernel_binary = { { kernel_binary, ELF::Image(kernel_binary->bytes()) } };
33 return &s_kernel_binary->elf;
34 }
35 return nullptr;
36}
37
38DisassemblyModel::DisassemblyModel(Profile& profile, ProfileNode& node)
39 : m_profile(profile)
40 , m_node(node)
41{
42 FlatPtr base_address = 0;
43 Debug::DebugInfo const* debug_info;
44 ELF::Image const* elf;
45 if (auto maybe_kernel_base = Symbolication::kernel_base(); maybe_kernel_base.has_value() && m_node.address() >= *maybe_kernel_base) {
46 if (!g_kernel_debuginfo_object.has_value())
47 return;
48 base_address = maybe_kernel_base.release_value();
49 elf = try_load_kernel_binary();
50 if (elf == nullptr)
51 return;
52 if (g_kernel_debug_info == nullptr)
53 g_kernel_debug_info = make<Debug::DebugInfo>(g_kernel_debuginfo_object->elf, DeprecatedString::empty(), base_address);
54 debug_info = g_kernel_debug_info.ptr();
55 } else {
56 auto const& process = node.process();
57 auto const* library_data = process.library_metadata.library_containing(node.address());
58 if (!library_data) {
59 dbgln("no library data for address {:p}", node.address());
60 return;
61 }
62 base_address = library_data->base;
63 elf = &library_data->object->elf;
64 debug_info = &library_data->load_debug_info(base_address);
65 }
66
67 VERIFY(elf != nullptr);
68 VERIFY(debug_info != nullptr);
69
70 FlatPtr function_address = node.address() - base_address;
71 auto is_function_address = false;
72 auto function = debug_info->get_containing_function(function_address);
73 if (function.has_value()) {
74 if (function_address == function->address_low)
75 is_function_address = true;
76 function_address = function->address_low;
77 } else {
78 dbgln("DisassemblyModel: Function containing {:p} ({}) not found", node.address() - base_address, node.symbol());
79 }
80
81 auto symbol = elf->find_symbol(function_address);
82 if (!symbol.has_value()) {
83 dbgln("DisassemblyModel: symbol not found");
84 return;
85 }
86 if (!symbol.value().raw_data().length()) {
87 dbgln("DisassemblyModel: Found symbol without code");
88 return;
89 }
90 VERIFY(symbol.has_value());
91
92 auto symbol_offset_from_function_start = node.address() - base_address - symbol->value();
93 auto view = symbol.value().raw_data().substring_view(symbol_offset_from_function_start);
94
95 X86::ELFSymbolProvider symbol_provider(*elf, base_address);
96 X86::SimpleInstructionStream stream((u8 const*)view.characters_without_null_termination(), view.length());
97 X86::Disassembler disassembler(stream);
98
99 size_t offset_into_symbol = 0;
100 FlatPtr last_instruction_offset = 0;
101 if (!is_function_address) {
102 FlatPtr last_instruction_address = 0;
103 for (auto const& event : node.events_per_address())
104 last_instruction_address = max(event.key, last_instruction_address);
105 last_instruction_offset = last_instruction_address - node.address();
106 }
107 for (;;) {
108 if (!is_function_address && offset_into_symbol > last_instruction_offset)
109 break;
110
111 auto insn = disassembler.next();
112 if (!insn.has_value())
113 break;
114 FlatPtr address_in_profiled_program = node.address() + offset_into_symbol;
115
116 auto disassembly = insn.value().to_deprecated_string(address_in_profiled_program, &symbol_provider);
117
118 StringView instruction_bytes = view.substring_view(offset_into_symbol, insn.value().length());
119 u32 samples_at_this_instruction = m_node.events_per_address().get(address_in_profiled_program).value_or(0);
120 float percent = ((float)samples_at_this_instruction / (float)m_node.event_count()) * 100.0f;
121 auto source_position = debug_info->get_source_position_with_inlines(address_in_profiled_program - base_address).release_value_but_fixme_should_propagate_errors();
122
123 m_instructions.append({ insn.value(), disassembly, instruction_bytes, address_in_profiled_program, samples_at_this_instruction, percent, source_position });
124
125 offset_into_symbol += insn.value().length();
126 }
127}
128
129int DisassemblyModel::row_count(GUI::ModelIndex const&) const
130{
131 return m_instructions.size();
132}
133
134DeprecatedString DisassemblyModel::column_name(int column) const
135{
136 switch (column) {
137 case Column::SampleCount:
138 return m_profile.show_percentages() ? "% Samples" : "# Samples";
139 case Column::Address:
140 return "Address";
141 case Column::InstructionBytes:
142 return "Insn Bytes";
143 case Column::Disassembly:
144 return "Disassembly";
145 case Column::SourceLocation:
146 return "Source Location";
147 default:
148 VERIFY_NOT_REACHED();
149 return {};
150 }
151}
152
153struct ColorPair {
154 Color background;
155 Color foreground;
156};
157
158static Optional<ColorPair> color_pair_for(InstructionData const& insn)
159{
160 if (insn.percent == 0)
161 return {};
162
163 Color background = color_for_percent(insn.percent);
164 Color foreground;
165 if (insn.percent > 50)
166 foreground = Color::White;
167 else
168 foreground = Color::Black;
169 return ColorPair { background, foreground };
170}
171
172GUI::Variant DisassemblyModel::data(GUI::ModelIndex const& index, GUI::ModelRole role) const
173{
174 auto const& insn = m_instructions[index.row()];
175
176 if (role == GUI::ModelRole::BackgroundColor) {
177 auto colors = color_pair_for(insn);
178 if (!colors.has_value())
179 return {};
180 return colors.value().background;
181 }
182
183 if (role == GUI::ModelRole::ForegroundColor) {
184 auto colors = color_pair_for(insn);
185 if (!colors.has_value())
186 return {};
187 return colors.value().foreground;
188 }
189
190 if (role == GUI::ModelRole::TextAlignment) {
191 if (index.column() == Column::SampleCount)
192 return Gfx::TextAlignment::CenterRight;
193 }
194
195 if (role == GUI::ModelRole::Display) {
196 if (index.column() == Column::SampleCount) {
197 if (m_profile.show_percentages())
198 return format_percentage(insn.event_count, m_node.event_count());
199 return insn.event_count;
200 }
201
202 if (index.column() == Column::Address)
203 return DeprecatedString::formatted("{:p}", insn.address);
204
205 if (index.column() == Column::InstructionBytes) {
206 StringBuilder builder;
207 for (auto ch : insn.bytes) {
208 builder.appendff("{:02x} ", (u8)ch);
209 }
210 return builder.to_deprecated_string();
211 }
212
213 if (index.column() == Column::Disassembly)
214 return insn.disassembly;
215
216 if (index.column() == Column::SourceLocation) {
217 StringBuilder builder;
218 auto first = true;
219 for (auto const& entry : insn.source_position_with_inlines.inline_chain) {
220 if (first)
221 first = false;
222 else
223 builder.append(" => "sv);
224 builder.appendff("{}:{}", entry.file_path, entry.line_number);
225 }
226 if (insn.source_position_with_inlines.source_position.has_value()) {
227 if (!first)
228 builder.append(" => "sv);
229 auto const& entry = insn.source_position_with_inlines.source_position.value();
230 builder.appendff("{}:{}", entry.file_path, entry.line_number);
231 }
232 return builder.to_deprecated_string();
233 }
234
235 return {};
236 }
237 return {};
238}
239
240}