summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
Diffstat (limited to 'Userland')
-rw-r--r--Userland/CMakeLists.txt1
-rw-r--r--Userland/DevTools/CMakeLists.txt5
-rw-r--r--Userland/DevTools/HackStudio/AutoCompleteResponse.h67
-rw-r--r--Userland/DevTools/HackStudio/CMakeLists.txt37
-rw-r--r--Userland/DevTools/HackStudio/CodeDocument.cpp68
-rw-r--r--Userland/DevTools/HackStudio/CodeDocument.h61
-rw-r--r--Userland/DevTools/HackStudio/CursorTool.cpp204
-rw-r--r--Userland/DevTools/HackStudio/CursorTool.h64
-rw-r--r--Userland/DevTools/HackStudio/Debugger/BacktraceModel.cpp78
-rw-r--r--Userland/DevTools/HackStudio/Debugger/BacktraceModel.h78
-rw-r--r--Userland/DevTools/HackStudio/Debugger/BreakpointCallback.h42
-rw-r--r--Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.cpp188
-rw-r--r--Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.h71
-rw-r--r--Userland/DevTools/HackStudio/Debugger/Debugger.cpp295
-rw-r--r--Userland/DevTools/HackStudio/Debugger/Debugger.h139
-rw-r--r--Userland/DevTools/HackStudio/Debugger/DisassemblyModel.cpp137
-rw-r--r--Userland/DevTools/HackStudio/Debugger/DisassemblyModel.h77
-rw-r--r--Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.cpp105
-rw-r--r--Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.h78
-rw-r--r--Userland/DevTools/HackStudio/Debugger/RegistersModel.cpp121
-rw-r--r--Userland/DevTools/HackStudio/Debugger/RegistersModel.h77
-rw-r--r--Userland/DevTools/HackStudio/Debugger/VariablesModel.cpp194
-rw-r--r--Userland/DevTools/HackStudio/Debugger/VariablesModel.h63
-rw-r--r--Userland/DevTools/HackStudio/Editor.cpp530
-rw-r--r--Userland/DevTools/HackStudio/Editor.h118
-rw-r--r--Userland/DevTools/HackStudio/EditorWrapper.cpp84
-rw-r--r--Userland/DevTools/HackStudio/EditorWrapper.h60
-rw-r--r--Userland/DevTools/HackStudio/FindInFilesWidget.cpp173
-rw-r--r--Userland/DevTools/HackStudio/FindInFilesWidget.h50
-rw-r--r--Userland/DevTools/HackStudio/FormEditorWidget.cpp68
-rw-r--r--Userland/DevTools/HackStudio/FormEditorWidget.h137
-rw-r--r--Userland/DevTools/HackStudio/FormWidget.cpp106
-rw-r--r--Userland/DevTools/HackStudio/FormWidget.h61
-rw-r--r--Userland/DevTools/HackStudio/Git/DiffViewer.cpp253
-rw-r--r--Userland/DevTools/HackStudio/Git/DiffViewer.h80
-rw-r--r--Userland/DevTools/HackStudio/Git/GitFilesModel.cpp56
-rw-r--r--Userland/DevTools/HackStudio/Git/GitFilesModel.h57
-rw-r--r--Userland/DevTools/HackStudio/Git/GitFilesView.cpp82
-rw-r--r--Userland/DevTools/HackStudio/Git/GitFilesView.h56
-rw-r--r--Userland/DevTools/HackStudio/Git/GitRepo.cpp151
-rw-r--r--Userland/DevTools/HackStudio/Git/GitRepo.h81
-rw-r--r--Userland/DevTools/HackStudio/Git/GitWidget.cpp188
-rw-r--r--Userland/DevTools/HackStudio/Git/GitWidget.h65
-rw-r--r--Userland/DevTools/HackStudio/HackStudio.h45
-rw-r--r--Userland/DevTools/HackStudio/HackStudioWidget.cpp945
-rw-r--r--Userland/DevTools/HackStudio/HackStudioWidget.h170
-rw-r--r--Userland/DevTools/HackStudio/Language.h38
-rw-r--r--Userland/DevTools/HackStudio/LanguageClient.cpp72
-rw-r--r--Userland/DevTools/HackStudio/LanguageClient.h122
-rw-r--r--Userland/DevTools/HackStudio/LanguageClients/CMakeLists.txt4
-rw-r--r--Userland/DevTools/HackStudio/LanguageClients/ServerConnections.h54
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/CMakeLists.txt5
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp97
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h51
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt15
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp177
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h64
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp55
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc4
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc11
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.cpp63
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.h50
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Shell/CMakeLists.txt15
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.cpp186
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h67
-rw-r--r--Userland/DevTools/HackStudio/LanguageServers/Shell/main.cpp63
-rw-r--r--Userland/DevTools/HackStudio/Locator.cpp178
-rw-r--r--Userland/DevTools/HackStudio/Locator.h52
-rw-r--r--Userland/DevTools/HackStudio/Project.cpp84
-rw-r--r--Userland/DevTools/HackStudio/Project.h64
-rw-r--r--Userland/DevTools/HackStudio/ProjectFile.cpp74
-rw-r--r--Userland/DevTools/HackStudio/ProjectFile.h62
-rw-r--r--Userland/DevTools/HackStudio/TerminalWrapper.cpp186
-rw-r--r--Userland/DevTools/HackStudio/TerminalWrapper.h56
-rw-r--r--Userland/DevTools/HackStudio/Tool.h63
-rw-r--r--Userland/DevTools/HackStudio/WidgetTool.cpp52
-rw-r--r--Userland/DevTools/HackStudio/WidgetTool.h52
-rw-r--r--Userland/DevTools/HackStudio/WidgetTreeModel.cpp117
-rw-r--r--Userland/DevTools/HackStudio/WidgetTreeModel.h55
-rw-r--r--Userland/DevTools/HackStudio/main.cpp171
-rw-r--r--Userland/DevTools/IPCCompiler/CMakeLists.txt6
-rw-r--r--Userland/DevTools/IPCCompiler/main.cpp613
-rw-r--r--Userland/DevTools/Inspector/CMakeLists.txt10
-rw-r--r--Userland/DevTools/Inspector/RemoteObject.cpp43
-rw-r--r--Userland/DevTools/Inspector/RemoteObject.h57
-rw-r--r--Userland/DevTools/Inspector/RemoteObjectGraphModel.cpp124
-rw-r--r--Userland/DevTools/Inspector/RemoteObjectGraphModel.h66
-rw-r--r--Userland/DevTools/Inspector/RemoteObjectPropertyModel.cpp241
-rw-r--r--Userland/DevTools/Inspector/RemoteObjectPropertyModel.h72
-rw-r--r--Userland/DevTools/Inspector/RemoteProcess.cpp201
-rw-r--r--Userland/DevTools/Inspector/RemoteProcess.h69
-rw-r--r--Userland/DevTools/Inspector/main.cpp152
-rw-r--r--Userland/DevTools/Playground/CMakeLists.txt6
-rw-r--r--Userland/DevTools/Playground/main.cpp407
-rw-r--r--Userland/DevTools/Profiler/CMakeLists.txt10
-rw-r--r--Userland/DevTools/Profiler/DisassemblyModel.cpp202
-rw-r--r--Userland/DevTools/Profiler/DisassemblyModel.h75
-rw-r--r--Userland/DevTools/Profiler/Profile.cpp399
-rw-r--r--Userland/DevTools/Profiler/Profile.h234
-rw-r--r--Userland/DevTools/Profiler/ProfileModel.cpp147
-rw-r--r--Userland/DevTools/Profiler/ProfileModel.h65
-rw-r--r--Userland/DevTools/Profiler/ProfileTimelineWidget.cpp110
-rw-r--r--Userland/DevTools/Profiler/ProfileTimelineWidget.h53
-rw-r--r--Userland/DevTools/Profiler/main.cpp220
-rw-r--r--Userland/DevTools/UserspaceEmulator/CMakeLists.txt14
-rw-r--r--Userland/DevTools/UserspaceEmulator/Emulator.cpp1816
-rw-r--r--Userland/DevTools/UserspaceEmulator/Emulator.h235
-rw-r--r--Userland/DevTools/UserspaceEmulator/MallocTracer.cpp379
-rw-r--r--Userland/DevTools/UserspaceEmulator/MallocTracer.h112
-rw-r--r--Userland/DevTools/UserspaceEmulator/MmapRegion.cpp224
-rw-r--r--Userland/DevTools/UserspaceEmulator/MmapRegion.h78
-rw-r--r--Userland/DevTools/UserspaceEmulator/Region.cpp39
-rw-r--r--Userland/DevTools/UserspaceEmulator/Region.h95
-rw-r--r--Userland/DevTools/UserspaceEmulator/Report.h40
-rw-r--r--Userland/DevTools/UserspaceEmulator/SharedBufferRegion.cpp132
-rw-r--r--Userland/DevTools/UserspaceEmulator/SharedBufferRegion.h68
-rw-r--r--Userland/DevTools/UserspaceEmulator/SimpleRegion.cpp103
-rw-r--r--Userland/DevTools/UserspaceEmulator/SimpleRegion.h58
-rw-r--r--Userland/DevTools/UserspaceEmulator/SoftCPU.cpp3282
-rw-r--r--Userland/DevTools/UserspaceEmulator/SoftCPU.h1202
-rw-r--r--Userland/DevTools/UserspaceEmulator/SoftMMU.cpp296
-rw-r--r--Userland/DevTools/UserspaceEmulator/SoftMMU.h102
-rw-r--r--Userland/DevTools/UserspaceEmulator/ValueWithShadow.h171
-rw-r--r--Userland/DevTools/UserspaceEmulator/main.cpp81
124 files changed, 20454 insertions, 0 deletions
diff --git a/Userland/CMakeLists.txt b/Userland/CMakeLists.txt
index 8d83e2237e..f99c8daaa9 100644
--- a/Userland/CMakeLists.txt
+++ b/Userland/CMakeLists.txt
@@ -1,5 +1,6 @@
add_subdirectory(Applications)
add_subdirectory(Demos)
+add_subdirectory(DevTools)
add_subdirectory(DynamicLoader)
add_subdirectory(Games)
add_subdirectory(Libraries)
diff --git a/Userland/DevTools/CMakeLists.txt b/Userland/DevTools/CMakeLists.txt
new file mode 100644
index 0000000000..b0d5aee0e3
--- /dev/null
+++ b/Userland/DevTools/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_subdirectory(HackStudio)
+add_subdirectory(Inspector)
+add_subdirectory(Playground)
+add_subdirectory(Profiler)
+add_subdirectory(UserspaceEmulator)
diff --git a/Userland/DevTools/HackStudio/AutoCompleteResponse.h b/Userland/DevTools/HackStudio/AutoCompleteResponse.h
new file mode 100644
index 0000000000..70023bf7ec
--- /dev/null
+++ b/Userland/DevTools/HackStudio/AutoCompleteResponse.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibGUI/AutocompleteProvider.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Encoder.h>
+
+namespace IPC {
+
+template<>
+inline bool encode(IPC::Encoder& encoder, const GUI::AutocompleteProvider::Entry& response)
+{
+ encoder << response.completion;
+ encoder << (u64)response.partial_input_length;
+ encoder << (u32)response.kind;
+ encoder << (u32)response.language;
+ return true;
+}
+
+template<>
+inline bool decode(IPC::Decoder& decoder, GUI::AutocompleteProvider::Entry& response)
+{
+ u32 kind = 0;
+ u32 language = 0;
+ u64 partial_input_length = 0;
+ bool ok = decoder.decode(response.completion)
+ && decoder.decode(partial_input_length)
+ && decoder.decode(kind)
+ && decoder.decode(language);
+
+ if (ok) {
+ response.kind = static_cast<GUI::AutocompleteProvider::CompletionKind>(kind);
+ response.language = static_cast<GUI::AutocompleteProvider::Language>(language);
+ response.partial_input_length = partial_input_length;
+ }
+
+ return ok;
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/CMakeLists.txt b/Userland/DevTools/HackStudio/CMakeLists.txt
new file mode 100644
index 0000000000..59406cbcbc
--- /dev/null
+++ b/Userland/DevTools/HackStudio/CMakeLists.txt
@@ -0,0 +1,37 @@
+add_subdirectory(LanguageServers)
+add_subdirectory(LanguageClients)
+
+set(SOURCES
+ CodeDocument.cpp
+ CursorTool.cpp
+ Debugger/BacktraceModel.cpp
+ Debugger/DebugInfoWidget.cpp
+ Debugger/Debugger.cpp
+ Debugger/DisassemblyModel.cpp
+ Debugger/DisassemblyWidget.cpp
+ Debugger/RegistersModel.cpp
+ Debugger/VariablesModel.cpp
+ Editor.cpp
+ EditorWrapper.cpp
+ FindInFilesWidget.cpp
+ FormEditorWidget.cpp
+ FormWidget.cpp
+ Git/DiffViewer.cpp
+ Git/GitFilesModel.cpp
+ Git/GitFilesView.cpp
+ Git/GitRepo.cpp
+ Git/GitWidget.cpp
+ HackStudioWidget.cpp
+ LanguageClient.cpp
+ Locator.cpp
+ Project.cpp
+ ProjectFile.cpp
+ TerminalWrapper.cpp
+ WidgetTool.cpp
+ WidgetTreeModel.cpp
+ main.cpp
+)
+
+serenity_app(HackStudio ICON app-hack-studio)
+target_link_libraries(HackStudio LibWeb LibMarkdown LibGUI LibGfx LibCore LibVT LibDebug LibX86 LibDiff LibShell)
+add_dependencies(HackStudio CppLanguageServer)
diff --git a/Userland/DevTools/HackStudio/CodeDocument.cpp b/Userland/DevTools/HackStudio/CodeDocument.cpp
new file mode 100644
index 0000000000..54f7e4d5f6
--- /dev/null
+++ b/Userland/DevTools/HackStudio/CodeDocument.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CodeDocument.h"
+
+namespace HackStudio {
+
+NonnullRefPtr<CodeDocument> CodeDocument::create(const String& file_path, Client* client)
+{
+ return adopt(*new CodeDocument(file_path, client));
+}
+
+NonnullRefPtr<CodeDocument> CodeDocument::create(Client* client)
+{
+ return adopt(*new CodeDocument(client));
+}
+
+CodeDocument::CodeDocument(const String& file_path, Client* client)
+ : TextDocument(client)
+ , m_file_path(file_path)
+{
+ LexicalPath lexical_path(file_path);
+
+ if (lexical_path.has_extension(".cpp") || lexical_path.has_extension(".h"))
+ m_language = Language::Cpp;
+ else if (lexical_path.has_extension(".js"))
+ m_language = Language::JavaScript;
+ else if (lexical_path.has_extension(".gml"))
+ m_language = Language::GML;
+ else if (lexical_path.has_extension(".ini"))
+ m_language = Language::Ini;
+ else if (lexical_path.has_extension(".sh"))
+ m_language = Language::Shell;
+}
+
+CodeDocument::CodeDocument(Client* client)
+ : TextDocument(client)
+{
+}
+
+CodeDocument::~CodeDocument()
+{
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/CodeDocument.h b/Userland/DevTools/HackStudio/CodeDocument.h
new file mode 100644
index 0000000000..a0e2f3136f
--- /dev/null
+++ b/Userland/DevTools/HackStudio/CodeDocument.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Language.h"
+#include <AK/LexicalPath.h>
+#include <LibGUI/TextDocument.h>
+
+namespace HackStudio {
+
+class CodeDocument final : public GUI::TextDocument {
+public:
+ virtual ~CodeDocument() override;
+ static NonnullRefPtr<CodeDocument> create(const String& file_path, Client* client = nullptr);
+ static NonnullRefPtr<CodeDocument> create(Client* client = nullptr);
+
+ const Vector<size_t>& breakpoint_lines() const { return m_breakpoint_lines; }
+ Vector<size_t>& breakpoint_lines() { return m_breakpoint_lines; }
+ Optional<size_t> execution_position() const { return m_execution_position; }
+ void set_execution_position(size_t line) { m_execution_position = line; }
+ void clear_execution_position() { m_execution_position.clear(); }
+ const String& file_path() const { return m_file_path; }
+ Language language() const { return m_language; }
+
+ virtual bool is_code_document() const override final { return true; }
+
+private:
+ explicit CodeDocument(const String& file_path, Client* client = nullptr);
+ explicit CodeDocument(Client* client = nullptr);
+
+ String m_file_path;
+ Language m_language { Language::Unknown };
+ Vector<size_t> m_breakpoint_lines;
+ Optional<size_t> m_execution_position;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/CursorTool.cpp b/Userland/DevTools/HackStudio/CursorTool.cpp
new file mode 100644
index 0000000000..b59963af7c
--- /dev/null
+++ b/Userland/DevTools/HackStudio/CursorTool.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "CursorTool.h"
+#include "FormEditorWidget.h"
+#include "FormWidget.h"
+#include "WidgetTreeModel.h"
+#include <AK/LogStream.h>
+#include <LibGfx/Palette.h>
+
+//#define DEBUG_CURSOR_TOOL
+
+namespace HackStudio {
+
+void CursorTool::on_mousedown(GUI::MouseEvent& event)
+{
+#ifdef DEBUG_CURSOR_TOOL
+ dbgln("CursorTool::on_mousedown");
+#endif
+ auto& form_widget = m_editor.form_widget();
+ auto result = form_widget.hit_test(event.position(), GUI::Widget::ShouldRespectGreediness::No);
+
+ if (event.button() == GUI::MouseButton::Left) {
+ if (result.widget && result.widget != &form_widget) {
+ if (event.modifiers() & Mod_Ctrl) {
+ m_editor.selection().toggle(*result.widget);
+ } else if (!event.modifiers()) {
+ if (!m_editor.selection().contains(*result.widget)) {
+#ifdef DEBUG_CURSOR_TOOL
+ dbg() << "Selection didn't contain " << *result.widget << ", making it the only selected one";
+#endif
+ m_editor.selection().set(*result.widget);
+ }
+
+ m_drag_origin = event.position();
+ m_positions_before_drag.clear();
+ m_editor.selection().for_each([&](auto& widget) {
+ m_positions_before_drag.set(&widget, widget.relative_position());
+ return IterationDecision::Continue;
+ });
+ }
+ } else {
+ m_editor.selection().clear();
+ m_rubber_banding = true;
+ m_rubber_band_origin = event.position();
+ m_rubber_band_position = event.position();
+ form_widget.update();
+ }
+ // FIXME: Do we need to update any part of the FormEditorWidget outside the FormWidget?
+ form_widget.update();
+ }
+}
+
+void CursorTool::on_mouseup(GUI::MouseEvent& event)
+{
+#ifdef DEBUG_CURSOR_TOOL
+ dbgln("CursorTool::on_mouseup");
+#endif
+ if (event.button() == GUI::MouseButton::Left) {
+ auto& form_widget = m_editor.form_widget();
+ auto result = form_widget.hit_test(event.position(), GUI::Widget::ShouldRespectGreediness::No);
+ if (!m_dragging && !(event.modifiers() & Mod_Ctrl)) {
+ if (result.widget && result.widget != &form_widget) {
+ m_editor.selection().set(*result.widget);
+ // FIXME: Do we need to update any part of the FormEditorWidget outside the FormWidget?
+ form_widget.update();
+ }
+ }
+ m_dragging = false;
+ m_rubber_banding = false;
+ form_widget.update();
+ }
+}
+
+void CursorTool::on_mousemove(GUI::MouseEvent& event)
+{
+#ifdef DEBUG_CURSOR_TOOL
+ dbgln("CursorTool::on_mousemove");
+#endif
+ auto& form_widget = m_editor.form_widget();
+
+ if (m_rubber_banding) {
+ set_rubber_band_position(event.position());
+ return;
+ }
+
+ if (!m_dragging && event.buttons() & GUI::MouseButton::Left && event.position() != m_drag_origin) {
+ auto result = form_widget.hit_test(event.position(), GUI::Widget::ShouldRespectGreediness::No);
+ if (result.widget && result.widget != &form_widget) {
+ if (!m_editor.selection().contains(*result.widget)) {
+ m_editor.selection().set(*result.widget);
+ // FIXME: Do we need to update any part of the FormEditorWidget outside the FormWidget?
+ form_widget.update();
+ }
+ }
+ m_dragging = true;
+ }
+
+ if (m_dragging) {
+ auto movement_delta = event.position() - m_drag_origin;
+ m_editor.selection().for_each([&](auto& widget) {
+ auto new_rect = widget.relative_rect();
+ new_rect.set_location(m_positions_before_drag.get(&widget).value_or({}).translated(movement_delta));
+ new_rect.set_x(new_rect.x() - (new_rect.x() % m_editor.form_widget().grid_size()));
+ new_rect.set_y(new_rect.y() - (new_rect.y() % m_editor.form_widget().grid_size()));
+ widget.set_relative_rect(new_rect);
+ return IterationDecision::Continue;
+ });
+ m_editor.model().update();
+ return;
+ }
+}
+
+void CursorTool::on_keydown(GUI::KeyEvent& event)
+{
+#ifdef DEBUG_CURSOR_TOOL
+ dbgln("CursorTool::on_keydown");
+#endif
+
+ auto move_selected_widgets_by = [this](int x, int y) {
+ m_editor.selection().for_each([&](auto& widget) {
+ widget.move_by(x, y);
+ return IterationDecision::Continue;
+ });
+ };
+
+ if (event.modifiers() == 0) {
+ switch (event.key()) {
+ case Key_Down:
+ move_selected_widgets_by(0, m_editor.form_widget().grid_size());
+ break;
+ case Key_Up:
+ move_selected_widgets_by(0, -m_editor.form_widget().grid_size());
+ break;
+ case Key_Left:
+ move_selected_widgets_by(-m_editor.form_widget().grid_size(), 0);
+ break;
+ case Key_Right:
+ move_selected_widgets_by(m_editor.form_widget().grid_size(), 0);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void CursorTool::set_rubber_band_position(const Gfx::IntPoint& position)
+{
+ if (m_rubber_band_position == position)
+ return;
+ m_rubber_band_position = position;
+
+ auto rubber_band_rect = this->rubber_band_rect();
+
+ m_editor.selection().clear();
+ m_editor.form_widget().for_each_child_widget([&](auto& child) {
+ if (child.relative_rect().intersects(rubber_band_rect))
+ m_editor.selection().add(child);
+ return IterationDecision::Continue;
+ });
+
+ m_editor.form_widget().update();
+}
+
+Gfx::IntRect CursorTool::rubber_band_rect() const
+{
+ if (!m_rubber_banding)
+ return {};
+ return Gfx::IntRect::from_two_points(m_rubber_band_origin, m_rubber_band_position);
+}
+
+void CursorTool::on_second_paint(GUI::Painter& painter, GUI::PaintEvent&)
+{
+ if (!m_rubber_banding)
+ return;
+ auto rect = rubber_band_rect();
+ painter.fill_rect(rect, m_editor.palette().rubber_band_fill());
+ painter.draw_rect(rect, m_editor.palette().rubber_band_border());
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/CursorTool.h b/Userland/DevTools/HackStudio/CursorTool.h
new file mode 100644
index 0000000000..cf216e77c4
--- /dev/null
+++ b/Userland/DevTools/HackStudio/CursorTool.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Tool.h"
+#include <AK/HashMap.h>
+#include <LibGUI/Forward.h>
+#include <LibGfx/Point.h>
+
+namespace HackStudio {
+
+class CursorTool final : public Tool {
+public:
+ explicit CursorTool(FormEditorWidget& editor)
+ : Tool(editor)
+ {
+ }
+ virtual ~CursorTool() override { }
+
+private:
+ virtual const char* class_name() const override { return "CursorTool"; }
+ virtual void on_mousedown(GUI::MouseEvent&) override;
+ virtual void on_mouseup(GUI::MouseEvent&) override;
+ virtual void on_mousemove(GUI::MouseEvent&) override;
+ virtual void on_keydown(GUI::KeyEvent&) override;
+ virtual void on_second_paint(GUI::Painter&, GUI::PaintEvent&) override;
+
+ void set_rubber_band_position(const Gfx::IntPoint&);
+ Gfx::IntRect rubber_band_rect() const;
+
+ Gfx::IntPoint m_drag_origin;
+ HashMap<GUI::Widget*, Gfx::IntPoint> m_positions_before_drag;
+ bool m_dragging { false };
+
+ bool m_rubber_banding { false };
+ Gfx::IntPoint m_rubber_band_origin;
+ Gfx::IntPoint m_rubber_band_position;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/BacktraceModel.cpp b/Userland/DevTools/HackStudio/Debugger/BacktraceModel.cpp
new file mode 100644
index 0000000000..fe0f6a31f5
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/BacktraceModel.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "BacktraceModel.h"
+#include "Debugger.h"
+#include <LibDebug/StackFrameUtils.h>
+
+namespace HackStudio {
+
+NonnullRefPtr<BacktraceModel> BacktraceModel::create(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
+{
+ return adopt(*new BacktraceModel(create_backtrace(debug_session, regs)));
+}
+
+GUI::Variant BacktraceModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ if (role == GUI::ModelRole::Display) {
+ auto& frame = m_frames.at(index.row());
+ return frame.function_name;
+ }
+ return {};
+}
+
+GUI::ModelIndex BacktraceModel::index(int row, int column, const GUI::ModelIndex&) const
+{
+ if (row < 0 || row >= static_cast<int>(m_frames.size()))
+ return {};
+ return create_index(row, column, &m_frames.at(row));
+}
+
+Vector<BacktraceModel::FrameInfo> BacktraceModel::create_backtrace(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
+{
+ u32 current_ebp = regs.ebp;
+ u32 current_instruction = regs.eip;
+ Vector<BacktraceModel::FrameInfo> frames;
+ do {
+ auto lib = debug_session.library_at(regs.eip);
+ if (!lib)
+ continue;
+ String name = lib->debug_info->name_of_containing_function(current_instruction - lib->base_address);
+ if (name.is_null()) {
+ dbgln("BacktraceModel: couldn't find containing function for address: {:p}", current_instruction);
+ name = "<missing>";
+ }
+
+ frames.append({ name, current_instruction, current_ebp });
+ auto frame_info = Debug::StackFrameUtils::get_info(*Debugger::the().session(), current_ebp);
+ ASSERT(frame_info.has_value());
+ current_instruction = frame_info.value().return_address;
+ current_ebp = frame_info.value().next_ebp;
+ } while (current_ebp && current_instruction);
+ return frames;
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/BacktraceModel.h b/Userland/DevTools/HackStudio/Debugger/BacktraceModel.h
new file mode 100644
index 0000000000..06d697c388
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/BacktraceModel.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibGUI/ListView.h>
+#include <LibGUI/Model.h>
+#include <sys/arch/i386/regs.h>
+
+namespace Debug {
+
+class DebugSession;
+
+}
+
+namespace HackStudio {
+
+class BacktraceModel final : public GUI::Model {
+public:
+ static NonnullRefPtr<BacktraceModel> create(const Debug::DebugSession&, const PtraceRegisters& regs);
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_frames.size(); }
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return 1; }
+
+ virtual String column_name(int) const override
+ {
+ return "";
+ }
+
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+
+ virtual void update() override { }
+ virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex&) const override;
+
+ struct FrameInfo {
+ String function_name;
+ u32 instruction_address;
+ u32 frame_base;
+ };
+
+ const Vector<FrameInfo>& frames() const { return m_frames; }
+
+private:
+ explicit BacktraceModel(Vector<FrameInfo>&& frames)
+ : m_frames(move(frames))
+ {
+ }
+
+ static Vector<FrameInfo> create_backtrace(const Debug::DebugSession&, const PtraceRegisters&);
+
+ Vector<FrameInfo> m_frames;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/BreakpointCallback.h b/Userland/DevTools/HackStudio/Debugger/BreakpointCallback.h
new file mode 100644
index 0000000000..6131e492ec
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/BreakpointCallback.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+
+namespace HackStudio {
+
+enum class BreakpointChange {
+ Added,
+ Removed,
+};
+
+typedef Function<void(const String& file, size_t line, BreakpointChange)> BreakpointChangeCallback;
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.cpp b/Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.cpp
new file mode 100644
index 0000000000..622538057d
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DebugInfoWidget.h"
+#include "BacktraceModel.h"
+#include "Debugger.h"
+#include "RegistersModel.h"
+#include "VariablesModel.h"
+#include <AK/StringBuilder.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/InputBox.h>
+#include <LibGUI/Layout.h>
+#include <LibGUI/ListView.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/Splitter.h>
+#include <LibGUI/TabWidget.h>
+#include <LibGUI/TreeView.h>
+
+namespace HackStudio {
+
+void DebugInfoWidget::init_toolbar()
+{
+ m_continue_action = GUI::Action::create("Continue", Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-continue.png"), [](auto&) {
+ Debugger::the().set_requested_debugger_action(Debugger::DebuggerAction::Continue);
+ });
+
+ m_singlestep_action = GUI::Action::create("Step Over", { Mod_None, Key_F10 }, Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-step-over.png"), [](auto&) {
+ Debugger::the().set_requested_debugger_action(Debugger::DebuggerAction::SourceStepOver);
+ });
+
+ m_step_in_action = GUI::Action::create("Step In", { Mod_None, Key_F11 }, Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-step-in.png"), [](auto&) {
+ Debugger::the().set_requested_debugger_action(Debugger::DebuggerAction::SourceSingleStep);
+ });
+
+ m_step_out_action = GUI::Action::create("Step Out", { Mod_Shift, Key_F11 }, Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-step-out.png"), [](auto&) {
+ Debugger::the().set_requested_debugger_action(Debugger::DebuggerAction::SourceStepOut);
+ });
+
+ m_toolbar->add_action(*m_continue_action);
+ m_toolbar->add_action(*m_singlestep_action);
+ m_toolbar->add_action(*m_step_in_action);
+ m_toolbar->add_action(*m_step_out_action);
+
+ set_debug_actions_enabled(false);
+}
+
+DebugInfoWidget::DebugInfoWidget()
+{
+ set_layout<GUI::VerticalBoxLayout>();
+ auto& toolbar_container = add<GUI::ToolBarContainer>();
+ m_toolbar = toolbar_container.add<GUI::ToolBar>();
+ init_toolbar();
+ auto& bottom_box = add<GUI::Widget>();
+ bottom_box.set_layout<GUI::HorizontalBoxLayout>();
+
+ auto& splitter = bottom_box.add<GUI::HorizontalSplitter>();
+ m_backtrace_view = splitter.add<GUI::ListView>();
+ auto& variables_tab_widget = splitter.add<GUI::TabWidget>();
+ variables_tab_widget.set_tab_position(GUI::TabWidget::TabPosition::Bottom);
+ variables_tab_widget.add_widget("Variables", build_variables_tab());
+ variables_tab_widget.add_widget("Registers", build_registers_tab());
+
+ m_backtrace_view->on_selection = [this](auto& index) {
+ auto& model = static_cast<BacktraceModel&>(*m_backtrace_view->model());
+
+ // Note: The reconstruction of the register set here is obviously incomplete.
+ // We currently only reconstruct eip & ebp. Ideally would also reconstruct the other registers somehow.
+ // (Other registers may be needed to get the values of variables who are not stored on the stack)
+ PtraceRegisters frame_regs {};
+ frame_regs.eip = model.frames()[index.row()].instruction_address;
+ frame_regs.ebp = model.frames()[index.row()].frame_base;
+
+ m_variables_view->set_model(VariablesModel::create(frame_regs));
+ };
+}
+
+NonnullRefPtr<GUI::Widget> DebugInfoWidget::build_variables_tab()
+{
+ auto variables_widget = GUI::Widget::construct();
+ variables_widget->set_layout<GUI::HorizontalBoxLayout>();
+
+ m_variables_view = variables_widget->add<GUI::TreeView>();
+
+ auto is_valid_index = [](auto& index) {
+ if (!index.is_valid())
+ return false;
+ auto* variable = static_cast<const Debug::DebugInfo::VariableInfo*>(index.internal_data());
+ if (variable->location_type != Debug::DebugInfo::VariableInfo::LocationType::Address)
+ return false;
+ return variable->is_enum_type() || variable->type_name.is_one_of("int", "bool");
+ };
+
+ m_variables_view->on_context_menu_request = [this, is_valid_index](auto& index, auto& event) {
+ if (!is_valid_index(index))
+ return;
+ m_variable_context_menu->popup(event.screen_position());
+ };
+
+ m_variables_view->on_activation = [this, is_valid_index](auto& index) {
+ if (!is_valid_index(index))
+ return;
+
+ String value;
+ if (GUI::InputBox::show(value, window(), "Enter new value:", "Set variable value") == GUI::InputBox::ExecOK) {
+ auto& model = static_cast<VariablesModel&>(*m_variables_view->model());
+ model.set_variable_value(index, value, window());
+ }
+ };
+
+ auto edit_variable_action = GUI::Action::create("Change value", [this](auto&) {
+ m_variables_view->on_activation(m_variables_view->selection().first());
+ });
+
+ m_variable_context_menu = GUI::Menu::construct();
+ m_variable_context_menu->add_action(edit_variable_action);
+
+ return variables_widget;
+}
+
+NonnullRefPtr<GUI::Widget> DebugInfoWidget::build_registers_tab()
+{
+ auto registers_widget = GUI::Widget::construct();
+ registers_widget->set_layout<GUI::HorizontalBoxLayout>();
+
+ m_registers_view = registers_widget->add<GUI::TableView>();
+
+ return registers_widget;
+}
+
+void DebugInfoWidget::update_state(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
+{
+ m_variables_view->set_model(VariablesModel::create(regs));
+ m_backtrace_view->set_model(BacktraceModel::create(debug_session, regs));
+ if (m_registers_view->model()) {
+ auto& previous_registers = static_cast<RegistersModel*>(m_registers_view->model())->raw_registers();
+ m_registers_view->set_model(RegistersModel::create(regs, previous_registers));
+ } else {
+ m_registers_view->set_model(RegistersModel::create(regs));
+ }
+ auto selected_index = m_backtrace_view->model()->index(0);
+ if (!selected_index.is_valid()) {
+ dbgln("Warning: DebugInfoWidget: backtrace selected index is invalid");
+ return;
+ }
+ m_backtrace_view->selection().set(selected_index);
+}
+
+void DebugInfoWidget::program_stopped()
+{
+ m_variables_view->set_model({});
+ m_backtrace_view->set_model({});
+ m_registers_view->set_model({});
+}
+
+void DebugInfoWidget::set_debug_actions_enabled(bool enabled)
+{
+ m_continue_action->set_enabled(enabled);
+ m_singlestep_action->set_enabled(enabled);
+ m_step_in_action->set_enabled(enabled);
+ m_step_out_action->set_enabled(enabled);
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.h b/Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.h
new file mode 100644
index 0000000000..efc6f4b958
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Debugger.h"
+#include <AK/NonnullOwnPtr.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/ListView.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/TableView.h>
+#include <LibGUI/ToolBar.h>
+#include <LibGUI/ToolBarContainer.h>
+#include <LibGUI/TreeView.h>
+#include <LibGUI/Widget.h>
+#include <sys/arch/i386/regs.h>
+
+namespace HackStudio {
+
+class DebugInfoWidget final : public GUI::Widget {
+ C_OBJECT(DebugInfoWidget)
+public:
+ virtual ~DebugInfoWidget() override { }
+
+ void update_state(const Debug::DebugSession&, const PtraceRegisters&);
+ void program_stopped();
+ void set_debug_actions_enabled(bool enabled);
+
+private:
+ explicit DebugInfoWidget();
+ void init_toolbar();
+
+ NonnullRefPtr<GUI::Widget> build_variables_tab();
+ NonnullRefPtr<GUI::Widget> build_registers_tab();
+
+ RefPtr<GUI::TreeView> m_variables_view;
+ RefPtr<GUI::TableView> m_registers_view;
+ RefPtr<GUI::ListView> m_backtrace_view;
+ RefPtr<GUI::Menu> m_variable_context_menu;
+ RefPtr<GUI::ToolBar> m_toolbar;
+ RefPtr<GUI::Action> m_continue_action;
+ RefPtr<GUI::Action> m_singlestep_action;
+ RefPtr<GUI::Action> m_step_in_action;
+ RefPtr<GUI::Action> m_step_out_action;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/Debugger.cpp b/Userland/DevTools/HackStudio/Debugger/Debugger.cpp
new file mode 100644
index 0000000000..20527e379d
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/Debugger.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Debugger.h"
+#include <LibDebug/StackFrameUtils.h>
+
+namespace HackStudio {
+
+static Debugger* s_the;
+
+Debugger& Debugger::the()
+{
+ ASSERT(s_the);
+ return *s_the;
+}
+
+void Debugger::initialize(
+ String source_root,
+ Function<HasControlPassedToUser(const PtraceRegisters&)> on_stop_callback,
+ Function<void()> on_continue_callback,
+ Function<void()> on_exit_callback)
+{
+ s_the = new Debugger(source_root, move(on_stop_callback), move(on_continue_callback), move(on_exit_callback));
+}
+
+bool Debugger::is_initialized()
+{
+ return s_the;
+}
+
+Debugger::Debugger(
+ String source_root,
+ Function<HasControlPassedToUser(const PtraceRegisters&)> on_stop_callback,
+ Function<void()> on_continue_callback,
+ Function<void()> on_exit_callback)
+ : m_source_root(source_root)
+ , m_on_stopped_callback(move(on_stop_callback))
+ , m_on_continue_callback(move(on_continue_callback))
+ , m_on_exit_callback(move(on_exit_callback))
+{
+ pthread_mutex_init(&m_ui_action_mutex, nullptr);
+ pthread_cond_init(&m_ui_action_cond, nullptr);
+}
+
+void Debugger::on_breakpoint_change(const String& file, size_t line, BreakpointChange change_type)
+{
+ auto position = create_source_position(file, line);
+
+ if (change_type == BreakpointChange::Added) {
+ Debugger::the().m_breakpoints.append(position);
+ } else {
+ Debugger::the().m_breakpoints.remove_all_matching([&](Debug::DebugInfo::SourcePosition val) { return val == position; });
+ }
+
+ auto session = Debugger::the().session();
+ if (!session)
+ return;
+
+ auto address = session->get_address_from_source_position(position.file_path, position.line_number);
+ if (!address.has_value()) {
+ dbgln("Warning: couldn't get instruction address from source");
+ // TODO: Currently, the GUI will indicate that a breakpoint was inserted/removed at this line,
+ // regardless of whether we actually succeeded to insert it. (For example a breakpoint on a comment, or an include statement).
+ // We should indicate failure via a return value from this function, and not update the breakpoint GUI if we fail.
+ return;
+ }
+
+ if (change_type == BreakpointChange::Added) {
+ bool success = session->insert_breakpoint(reinterpret_cast<void*>(address.value().address));
+ ASSERT(success);
+ } else {
+ bool success = session->remove_breakpoint(reinterpret_cast<void*>(address.value().address));
+ ASSERT(success);
+ }
+}
+
+Debug::DebugInfo::SourcePosition Debugger::create_source_position(const String& file, size_t line)
+{
+ if (!file.starts_with('/') && !file.starts_with("./"))
+ return { String::formatted("./{}", file), line + 1 };
+ return { file, line + 1 };
+}
+
+int Debugger::start_static()
+{
+ Debugger::the().start();
+ return 0;
+}
+
+void Debugger::start()
+{
+ m_debug_session = Debug::DebugSession::exec_and_attach(m_executable_path, m_source_root);
+ ASSERT(!!m_debug_session);
+
+ for (const auto& breakpoint : m_breakpoints) {
+ dbgln("inserting breakpoint at: {}:{}", breakpoint.file_path, breakpoint.line_number);
+ auto address = m_debug_session->get_address_from_source_position(breakpoint.file_path, breakpoint.line_number);
+ if (address.has_value()) {
+ bool success = m_debug_session->insert_breakpoint(reinterpret_cast<void*>(address.value().address));
+ ASSERT(success);
+ } else {
+ dbgln("couldn't insert breakpoint");
+ }
+ }
+
+ debugger_loop();
+}
+
+int Debugger::debugger_loop()
+{
+ ASSERT(m_debug_session);
+
+ m_debug_session->run(Debug::DebugSession::DesiredInitialDebugeeState::Running, [this](Debug::DebugSession::DebugBreakReason reason, Optional<PtraceRegisters> optional_regs) {
+ if (reason == Debug::DebugSession::DebugBreakReason::Exited) {
+ dbgln("Program exited");
+ m_on_exit_callback();
+ return Debug::DebugSession::DebugDecision::Detach;
+ }
+ remove_temporary_breakpoints();
+ ASSERT(optional_regs.has_value());
+ const PtraceRegisters& regs = optional_regs.value();
+
+ auto source_position = m_debug_session->get_source_position(regs.eip);
+ if (!source_position.has_value())
+ return Debug::DebugSession::DebugDecision::SingleStep;
+
+ // We currently do no support stepping through assembly source
+ if (source_position.value().file_path.ends_with(".S"))
+ return Debug::DebugSession::DebugDecision::SingleStep;
+
+ ASSERT(source_position.has_value());
+ if (m_state.get() == Debugger::DebuggingState::SingleStepping) {
+ if (m_state.should_stop_single_stepping(source_position.value())) {
+ m_state.set_normal();
+ } else {
+ return Debug::DebugSession::DebugDecision::SingleStep;
+ }
+ }
+
+ auto control_passed_to_user = m_on_stopped_callback(regs);
+
+ if (control_passed_to_user == HasControlPassedToUser::Yes) {
+ pthread_mutex_lock(&m_ui_action_mutex);
+ pthread_cond_wait(&m_ui_action_cond, &m_ui_action_mutex);
+ pthread_mutex_unlock(&m_ui_action_mutex);
+
+ if (m_requested_debugger_action != DebuggerAction::Exit)
+ m_on_continue_callback();
+
+ } else {
+ m_requested_debugger_action = DebuggerAction::Continue;
+ }
+
+ switch (m_requested_debugger_action) {
+ case DebuggerAction::Continue:
+ m_state.set_normal();
+ return Debug::DebugSession::DebugDecision::Continue;
+ case DebuggerAction::SourceSingleStep:
+ m_state.set_single_stepping(source_position.value());
+ return Debug::DebugSession::DebugDecision::SingleStep;
+ case DebuggerAction::SourceStepOut:
+ m_state.set_stepping_out();
+ do_step_out(regs);
+ return Debug::DebugSession::DebugDecision::Continue;
+ case DebuggerAction::SourceStepOver:
+ m_state.set_stepping_over();
+ do_step_over(regs);
+ return Debug::DebugSession::DebugDecision::Continue;
+ case DebuggerAction::Exit:
+ // NOTE: Is detaching from the debuggee the best thing to do here?
+ // We could display a dialog in the UI, remind the user that there is
+ // a live debugged process, and ask whether they want to terminate/detach.
+ dbgln("Debugger exiting");
+ return Debug::DebugSession::DebugDecision::Detach;
+ }
+ ASSERT_NOT_REACHED();
+ });
+ m_debug_session.clear();
+ return 0;
+}
+
+void Debugger::DebuggingState::set_normal()
+{
+ m_state = State::Normal;
+ m_original_source_position.clear();
+}
+
+void Debugger::DebuggingState::set_single_stepping(Debug::DebugInfo::SourcePosition original_source_position)
+{
+ m_state = State::SingleStepping;
+ m_original_source_position = original_source_position;
+}
+
+bool Debugger::DebuggingState::should_stop_single_stepping(const Debug::DebugInfo::SourcePosition& current_source_position) const
+{
+ ASSERT(m_state == State::SingleStepping);
+ return m_original_source_position.value() != current_source_position;
+}
+
+void Debugger::remove_temporary_breakpoints()
+{
+ for (auto breakpoint_address : m_state.temporary_breakpoints()) {
+ ASSERT(m_debug_session->breakpoint_exists((void*)breakpoint_address));
+ bool rc = m_debug_session->remove_breakpoint((void*)breakpoint_address);
+ ASSERT(rc);
+ }
+ m_state.clear_temporary_breakpoints();
+}
+
+void Debugger::DebuggingState::clear_temporary_breakpoints()
+{
+ m_addresses_of_temporary_breakpoints.clear();
+}
+void Debugger::DebuggingState::add_temporary_breakpoint(u32 address)
+{
+ m_addresses_of_temporary_breakpoints.append(address);
+}
+
+void Debugger::do_step_out(const PtraceRegisters& regs)
+{
+ // To step out, we simply insert a temporary breakpoint at the
+ // instruction the current function returns to, and continue
+ // execution until we hit that instruction (or some other breakpoint).
+ insert_temporary_breakpoint_at_return_address(regs);
+}
+
+void Debugger::do_step_over(const PtraceRegisters& regs)
+{
+ // To step over, we insert a temporary breakpoint at each line in the current function,
+ // as well as at the current function's return point, and continue execution.
+ auto lib = m_debug_session->library_at(regs.eip);
+ if (!lib)
+ return;
+ auto current_function = lib->debug_info->get_containing_function(regs.eip - lib->base_address);
+ if (!current_function.has_value()) {
+ dbgln("cannot perform step_over, failed to find containing function of: {:p}", regs.eip);
+ return;
+ }
+ ASSERT(current_function.has_value());
+ auto lines_in_current_function = lib->debug_info->source_lines_in_scope(current_function.value());
+ for (const auto& line : lines_in_current_function) {
+ insert_temporary_breakpoint(line.address_of_first_statement.value() + lib->base_address);
+ }
+ insert_temporary_breakpoint_at_return_address(regs);
+}
+
+void Debugger::insert_temporary_breakpoint_at_return_address(const PtraceRegisters& regs)
+{
+ auto frame_info = Debug::StackFrameUtils::get_info(*m_debug_session, regs.ebp);
+ ASSERT(frame_info.has_value());
+ u32 return_address = frame_info.value().return_address;
+ insert_temporary_breakpoint(return_address);
+}
+
+void Debugger::insert_temporary_breakpoint(FlatPtr address)
+{
+ if (m_debug_session->breakpoint_exists((void*)address))
+ return;
+ bool success = m_debug_session->insert_breakpoint(reinterpret_cast<void*>(address));
+ ASSERT(success);
+ m_state.add_temporary_breakpoint(address);
+}
+
+void Debugger::set_requested_debugger_action(DebuggerAction action)
+{
+ pthread_mutex_lock(continue_mutex());
+ m_requested_debugger_action = action;
+ pthread_cond_signal(continue_cond());
+ pthread_mutex_unlock(continue_mutex());
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/Debugger.h b/Userland/DevTools/HackStudio/Debugger/Debugger.h
new file mode 100644
index 0000000000..0d2371f86e
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/Debugger.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "BreakpointCallback.h"
+#include <AK/Function.h>
+#include <AK/Vector.h>
+#include <LibDebug/DebugSession.h>
+#include <LibThread/Lock.h>
+#include <LibThread/Thread.h>
+
+namespace HackStudio {
+
+class Debugger {
+public:
+ static Debugger& the();
+
+ enum class HasControlPassedToUser {
+ No,
+ Yes,
+ };
+
+ static void initialize(
+ String source_root,
+ Function<HasControlPassedToUser(const PtraceRegisters&)> on_stop_callback,
+ Function<void()> on_continue_callback,
+ Function<void()> on_exit_callback);
+
+ static bool is_initialized();
+
+ static void on_breakpoint_change(const String& file, size_t line, BreakpointChange change_type);
+
+ void set_executable_path(const String& path) { m_executable_path = path; }
+
+ Debug::DebugSession* session() { return m_debug_session.ptr(); }
+
+ // Thread entry point
+ static int start_static();
+
+ pthread_mutex_t* continue_mutex() { return &m_ui_action_mutex; }
+ pthread_cond_t* continue_cond() { return &m_ui_action_cond; }
+
+ enum class DebuggerAction {
+ Continue,
+ SourceSingleStep,
+ SourceStepOut,
+ SourceStepOver,
+ Exit,
+ };
+
+ void set_requested_debugger_action(DebuggerAction);
+ void reset_breakpoints() { m_breakpoints.clear(); }
+
+private:
+ class DebuggingState {
+ public:
+ enum State {
+ Normal, // Continue normally until we hit a breakpoint / program terminates
+ SingleStepping,
+ SteppingOut,
+ SteppingOver,
+ };
+ State get() const { return m_state; }
+
+ void set_normal();
+ void set_single_stepping(Debug::DebugInfo::SourcePosition original_source_position);
+ void set_stepping_out() { m_state = State::SteppingOut; }
+ void set_stepping_over() { m_state = State::SteppingOver; }
+
+ bool should_stop_single_stepping(const Debug::DebugInfo::SourcePosition& current_source_position) const;
+ void clear_temporary_breakpoints();
+ void add_temporary_breakpoint(u32 address);
+ const Vector<u32>& temporary_breakpoints() const { return m_addresses_of_temporary_breakpoints; }
+
+ private:
+ State m_state { Normal };
+ Optional<Debug::DebugInfo::SourcePosition> m_original_source_position; // The source position at which we started the current single step
+ Vector<u32> m_addresses_of_temporary_breakpoints;
+ };
+
+ explicit Debugger(
+ String source_root,
+ Function<HasControlPassedToUser(const PtraceRegisters&)> on_stop_callback,
+ Function<void()> on_continue_callback,
+ Function<void()> on_exit_callback);
+
+ static Debug::DebugInfo::SourcePosition create_source_position(const String& file, size_t line);
+
+ void start();
+ int debugger_loop();
+
+ void remove_temporary_breakpoints();
+ void do_step_out(const PtraceRegisters&);
+ void do_step_over(const PtraceRegisters&);
+ void insert_temporary_breakpoint(FlatPtr address);
+ void insert_temporary_breakpoint_at_return_address(const PtraceRegisters&);
+
+ OwnPtr<Debug::DebugSession> m_debug_session;
+ String m_source_root;
+ DebuggingState m_state;
+
+ pthread_mutex_t m_ui_action_mutex {};
+ pthread_cond_t m_ui_action_cond {};
+ DebuggerAction m_requested_debugger_action { DebuggerAction::Continue };
+
+ Vector<Debug::DebugInfo::SourcePosition> m_breakpoints;
+
+ String m_executable_path;
+
+ Function<HasControlPassedToUser(const PtraceRegisters&)> m_on_stopped_callback;
+ Function<void()> m_on_continue_callback;
+ Function<void()> m_on_exit_callback;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/DisassemblyModel.cpp b/Userland/DevTools/HackStudio/Debugger/DisassemblyModel.cpp
new file mode 100644
index 0000000000..cb7822c60a
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/DisassemblyModel.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DisassemblyModel.h"
+#include <AK/MappedFile.h>
+#include <AK/StringBuilder.h>
+#include <LibDebug/DebugSession.h>
+#include <LibELF/Image.h>
+#include <LibX86/Disassembler.h>
+#include <LibX86/ELFSymbolProvider.h>
+#include <ctype.h>
+#include <stdio.h>
+
+namespace HackStudio {
+
+DisassemblyModel::DisassemblyModel(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
+{
+ auto lib = debug_session.library_at(regs.eip);
+ if (!lib)
+ return;
+ auto containing_function = lib->debug_info->get_containing_function(regs.eip - lib->base_address);
+ if (!containing_function.has_value()) {
+ dbgln("Cannot disassemble as the containing function was not found.");
+ return;
+ }
+
+ OwnPtr<ELF::Image> kernel_elf;
+ const ELF::Image* elf = nullptr;
+
+ if (containing_function.value().address_low >= 0xc0000000) {
+ auto file_or_error = MappedFile::map("/boot/Kernel");
+ if (file_or_error.is_error())
+ return;
+ kernel_elf = make<ELF::Image>(file_or_error.value()->bytes());
+ elf = kernel_elf.ptr();
+ } else {
+ elf = &lib->debug_info->elf();
+ }
+
+ auto symbol = elf->find_symbol(containing_function.value().address_low);
+ if (!symbol.has_value())
+ return;
+ ASSERT(symbol.has_value());
+
+ auto view = symbol.value().raw_data();
+
+ X86::ELFSymbolProvider symbol_provider(*elf);
+ X86::SimpleInstructionStream stream((const u8*)view.characters_without_null_termination(), view.length());
+ X86::Disassembler disassembler(stream);
+
+ size_t offset_into_symbol = 0;
+ for (;;) {
+ auto insn = disassembler.next();
+ if (!insn.has_value())
+ break;
+ FlatPtr address_in_profiled_program = symbol.value().value() + offset_into_symbol;
+ auto disassembly = insn.value().to_string(address_in_profiled_program, &symbol_provider);
+ StringView instruction_bytes = view.substring_view(offset_into_symbol, insn.value().length());
+ m_instructions.append({ insn.value(), disassembly, instruction_bytes, address_in_profiled_program });
+
+ offset_into_symbol += insn.value().length();
+ }
+}
+
+DisassemblyModel::~DisassemblyModel()
+{
+}
+
+int DisassemblyModel::row_count(const GUI::ModelIndex&) const
+{
+ return m_instructions.size();
+}
+
+String DisassemblyModel::column_name(int column) const
+{
+ switch (column) {
+ case Column::Address:
+ return "Address";
+ case Column::InstructionBytes:
+ return "Insn Bytes";
+ case Column::Disassembly:
+ return "Disassembly";
+ default:
+ ASSERT_NOT_REACHED();
+ return {};
+ }
+}
+
+GUI::Variant DisassemblyModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto& insn = m_instructions[index.row()];
+
+ if (role == GUI::ModelRole::Display) {
+ if (index.column() == Column::Address)
+ return String::formatted("{:p}", insn.address);
+ if (index.column() == Column::InstructionBytes) {
+ StringBuilder builder;
+ for (auto ch : insn.bytes)
+ builder.appendff("{:02x} ", static_cast<unsigned char>(ch));
+ return builder.to_string();
+ }
+ if (index.column() == Column::Disassembly)
+ return insn.disassembly;
+ return {};
+ }
+ return {};
+}
+
+void DisassemblyModel::update()
+{
+ did_update();
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/DisassemblyModel.h b/Userland/DevTools/HackStudio/Debugger/DisassemblyModel.h
new file mode 100644
index 0000000000..c09d23a04a
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/DisassemblyModel.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibGUI/Model.h>
+#include <LibX86/Instruction.h>
+#include <sys/arch/i386/regs.h>
+
+namespace Debug {
+
+class DebugSession;
+
+}
+
+namespace HackStudio {
+
+struct InstructionData {
+ X86::Instruction insn;
+ String disassembly;
+ StringView bytes;
+ FlatPtr address { 0 };
+};
+
+class DisassemblyModel final : public GUI::Model {
+public:
+ static NonnullRefPtr<DisassemblyModel> create(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
+ {
+ return adopt(*new DisassemblyModel(debug_session, regs));
+ }
+
+ enum Column {
+ Address,
+ InstructionBytes,
+ Disassembly,
+ __Count
+ };
+
+ virtual ~DisassemblyModel() override;
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Count; }
+ virtual String column_name(int) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+ virtual void update() override;
+
+private:
+ DisassemblyModel(const Debug::DebugSession&, const PtraceRegisters&);
+
+ Vector<InstructionData> m_instructions;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.cpp b/Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.cpp
new file mode 100644
index 0000000000..89a670b2a2
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DisassemblyWidget.h"
+#include "DisassemblyModel.h"
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Palette.h>
+
+namespace HackStudio {
+
+void UnavailableDisassemblyWidget::paint_event(GUI::PaintEvent& event)
+{
+ Frame::paint_event(event);
+ if (reason().is_empty())
+ return;
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ painter.draw_text(frame_inner_rect(), reason(), Gfx::TextAlignment::Center, palette().window_text(), Gfx::TextElision::Right);
+}
+
+DisassemblyWidget::DisassemblyWidget()
+{
+ set_layout<GUI::VerticalBoxLayout>();
+
+ m_top_container = add<GUI::Widget>();
+ m_top_container->set_layout<GUI::HorizontalBoxLayout>();
+ m_top_container->set_fixed_height(20);
+
+ m_function_name_label = m_top_container->add<GUI::Label>("");
+
+ m_disassembly_view = add<GUI::TableView>();
+
+ m_unavailable_disassembly_widget = add<UnavailableDisassemblyWidget>("");
+
+ hide_disassembly("Program isn't running");
+}
+
+void DisassemblyWidget::update_state(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
+{
+ m_disassembly_view->set_model(DisassemblyModel::create(debug_session, regs));
+
+ if (m_disassembly_view->model()->row_count() > 0) {
+ auto lib = debug_session.library_at(regs.eip);
+ if (!lib)
+ return;
+ auto containing_function = lib->debug_info->get_containing_function(regs.eip - lib->base_address);
+ if (containing_function.has_value())
+ m_function_name_label->set_text(containing_function.value().name);
+ else
+ m_function_name_label->set_text("<missing>");
+ show_disassembly();
+ } else {
+ hide_disassembly("No disassembly to show for this function");
+ }
+}
+
+void DisassemblyWidget::program_stopped()
+{
+ m_disassembly_view->set_model({});
+ m_function_name_label->set_text("");
+ hide_disassembly("Program isn't running");
+}
+
+void DisassemblyWidget::show_disassembly()
+{
+ m_top_container->set_visible(true);
+ m_disassembly_view->set_visible(true);
+ m_function_name_label->set_visible(true);
+ m_unavailable_disassembly_widget->set_visible(false);
+}
+
+void DisassemblyWidget::hide_disassembly(const String& reason)
+{
+ m_top_container->set_visible(false);
+ m_disassembly_view->set_visible(false);
+ m_function_name_label->set_visible(false);
+ m_unavailable_disassembly_widget->set_visible(true);
+ m_unavailable_disassembly_widget->set_reason(reason);
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.h b/Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.h
new file mode 100644
index 0000000000..77d36b0edd
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Debugger.h"
+#include <AK/NonnullOwnPtr.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/TableView.h>
+#include <LibGUI/Widget.h>
+#include <sys/arch/i386/regs.h>
+
+namespace HackStudio {
+
+class UnavailableDisassemblyWidget final : public GUI::Frame {
+ C_OBJECT(UnavailableDisassemblyWidget)
+public:
+ virtual ~UnavailableDisassemblyWidget() override { }
+
+ const String& reason() const { return m_reason; }
+ void set_reason(const String& text) { m_reason = text; }
+
+private:
+ UnavailableDisassemblyWidget(const String& reason)
+ : m_reason(reason)
+ {
+ }
+
+ virtual void paint_event(GUI::PaintEvent& event) override;
+
+ String m_reason;
+};
+
+class DisassemblyWidget final : public GUI::Widget {
+ C_OBJECT(DisassemblyWidget)
+public:
+ virtual ~DisassemblyWidget() override { }
+
+ void update_state(const Debug::DebugSession&, const PtraceRegisters&);
+ void program_stopped();
+
+private:
+ DisassemblyWidget();
+
+ void show_disassembly();
+ void hide_disassembly(const String&);
+
+ RefPtr<GUI::Widget> m_top_container;
+ RefPtr<GUI::TableView> m_disassembly_view;
+ RefPtr<GUI::Label> m_function_name_label;
+ RefPtr<UnavailableDisassemblyWidget> m_unavailable_disassembly_widget;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/RegistersModel.cpp b/Userland/DevTools/HackStudio/Debugger/RegistersModel.cpp
new file mode 100644
index 0000000000..92199a4c6f
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/RegistersModel.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RegistersModel.h"
+
+namespace HackStudio {
+
+RegistersModel::RegistersModel(const PtraceRegisters& regs)
+ : m_raw_registers(regs)
+{
+ m_registers.append({ "eax", regs.eax });
+ m_registers.append({ "ebx", regs.ebx });
+ m_registers.append({ "ecx", regs.ecx });
+ m_registers.append({ "edx", regs.edx });
+ m_registers.append({ "esp", regs.esp });
+ m_registers.append({ "ebp", regs.ebp });
+ m_registers.append({ "esi", regs.esi });
+ m_registers.append({ "edi", regs.edi });
+ m_registers.append({ "eip", regs.eip });
+ m_registers.append({ "eflags", regs.eflags });
+ m_registers.append({ "cs", regs.cs });
+ m_registers.append({ "ss", regs.ss });
+ m_registers.append({ "ds", regs.ds });
+ m_registers.append({ "es", regs.es });
+ m_registers.append({ "fs", regs.fs });
+ m_registers.append({ "gs", regs.gs });
+}
+
+RegistersModel::RegistersModel(const PtraceRegisters& current_regs, const PtraceRegisters& previous_regs)
+ : m_raw_registers(current_regs)
+{
+ m_registers.append({ "eax", current_regs.eax, current_regs.eax != previous_regs.eax });
+ m_registers.append({ "ebx", current_regs.ebx, current_regs.ebx != previous_regs.ebx });
+ m_registers.append({ "ecx", current_regs.ecx, current_regs.ecx != previous_regs.ecx });
+ m_registers.append({ "edx", current_regs.edx, current_regs.edx != previous_regs.edx });
+ m_registers.append({ "esp", current_regs.esp, current_regs.esp != previous_regs.esp });
+ m_registers.append({ "ebp", current_regs.ebp, current_regs.ebp != previous_regs.ebp });
+ m_registers.append({ "esi", current_regs.esi, current_regs.esi != previous_regs.esi });
+ m_registers.append({ "edi", current_regs.edi, current_regs.edi != previous_regs.edi });
+ m_registers.append({ "eip", current_regs.eip, current_regs.eip != previous_regs.eip });
+ m_registers.append({ "eflags", current_regs.eflags, current_regs.eflags != previous_regs.eflags });
+ m_registers.append({ "cs", current_regs.cs, current_regs.cs != previous_regs.cs });
+ m_registers.append({ "ss", current_regs.ss, current_regs.ss != previous_regs.ss });
+ m_registers.append({ "ds", current_regs.ds, current_regs.ds != previous_regs.ds });
+ m_registers.append({ "es", current_regs.es, current_regs.es != previous_regs.es });
+ m_registers.append({ "fs", current_regs.fs, current_regs.ds != previous_regs.fs });
+ m_registers.append({ "gs", current_regs.gs, current_regs.gs != previous_regs.gs });
+}
+
+RegistersModel::~RegistersModel()
+{
+}
+
+int RegistersModel::row_count(const GUI::ModelIndex&) const
+{
+ return m_registers.size();
+}
+
+String RegistersModel::column_name(int column) const
+{
+ switch (column) {
+ case Column::Register:
+ return "Register";
+ case Column::Value:
+ return "Value";
+ default:
+ ASSERT_NOT_REACHED();
+ return {};
+ }
+}
+
+GUI::Variant RegistersModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto& reg = m_registers[index.row()];
+
+ if (role == GUI::ModelRole::ForegroundColor) {
+ if (reg.changed)
+ return Color(Color::Red);
+ else
+ return Color(Color::Black);
+ }
+
+ if (role == GUI::ModelRole::Display) {
+ if (index.column() == Column::Register)
+ return reg.name;
+ if (index.column() == Column::Value)
+ return String::formatted("{:08x}", reg.value);
+ return {};
+ }
+ return {};
+}
+
+void RegistersModel::update()
+{
+ did_update();
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/RegistersModel.h b/Userland/DevTools/HackStudio/Debugger/RegistersModel.h
new file mode 100644
index 0000000000..8252f16943
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/RegistersModel.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <LibGUI/Model.h>
+#include <sys/arch/i386/regs.h>
+
+namespace HackStudio {
+
+struct RegisterData {
+ String name;
+ u32 value;
+ bool changed { false };
+};
+
+class RegistersModel final : public GUI::Model {
+public:
+ static RefPtr<RegistersModel> create(const PtraceRegisters& regs)
+ {
+ return adopt(*new RegistersModel(regs));
+ }
+
+ static RefPtr<RegistersModel> create(const PtraceRegisters& current_regs, const PtraceRegisters& previous_regs)
+ {
+ return adopt(*new RegistersModel(current_regs, previous_regs));
+ }
+
+ enum Column {
+ Register,
+ Value,
+ __Count
+ };
+
+ virtual ~RegistersModel() override;
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Count; }
+ virtual String column_name(int) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+ virtual void update() override;
+
+ const PtraceRegisters& raw_registers() const { return m_raw_registers; }
+
+private:
+ explicit RegistersModel(const PtraceRegisters& regs);
+ RegistersModel(const PtraceRegisters& current_regs, const PtraceRegisters& previous_regs);
+
+ PtraceRegisters m_raw_registers;
+ Vector<RegisterData> m_registers;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/VariablesModel.cpp b/Userland/DevTools/HackStudio/Debugger/VariablesModel.cpp
new file mode 100644
index 0000000000..425129046c
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/VariablesModel.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "VariablesModel.h"
+#include <LibGUI/Application.h>
+#include <LibGUI/MessageBox.h>
+
+namespace HackStudio {
+
+GUI::ModelIndex VariablesModel::index(int row, int column, const GUI::ModelIndex& parent_index) const
+{
+ if (!parent_index.is_valid())
+ return create_index(row, column, &m_variables[row]);
+ auto* parent = static_cast<const Debug::DebugInfo::VariableInfo*>(parent_index.internal_data());
+ auto* child = &parent->members[row];
+ return create_index(row, column, child);
+}
+
+GUI::ModelIndex VariablesModel::parent_index(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+ auto* child = static_cast<const Debug::DebugInfo::VariableInfo*>(index.internal_data());
+ auto* parent = child->parent;
+ if (parent == nullptr)
+ return {};
+
+ if (parent->parent == nullptr) {
+ for (size_t row = 0; row < m_variables.size(); row++)
+ if (m_variables.ptr_at(row).ptr() == parent)
+ return create_index(row, 0, parent);
+ ASSERT_NOT_REACHED();
+ }
+ for (size_t row = 0; row < parent->parent->members.size(); row++) {
+ Debug::DebugInfo::VariableInfo* child_at_row = parent->parent->members.ptr_at(row).ptr();
+ if (child_at_row == parent)
+ return create_index(row, 0, parent);
+ }
+ ASSERT_NOT_REACHED();
+}
+
+int VariablesModel::row_count(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return m_variables.size();
+ auto* node = static_cast<const Debug::DebugInfo::VariableInfo*>(index.internal_data());
+ return node->members.size();
+}
+
+static String variable_value_as_string(const Debug::DebugInfo::VariableInfo& variable)
+{
+ if (variable.location_type != Debug::DebugInfo::VariableInfo::LocationType::Address)
+ return "N/A";
+
+ auto variable_address = variable.location_data.address;
+
+ if (variable.is_enum_type()) {
+ auto value = Debugger::the().session()->peek((u32*)variable_address);
+ ASSERT(value.has_value());
+ auto it = variable.type->members.find_if([&enumerator_value = value.value()](const auto& enumerator) {
+ return enumerator->constant_data.as_u32 == enumerator_value;
+ });
+ ASSERT(!it.is_end());
+ return String::formatted("{}::{}", variable.type_name, (*it)->name);
+ }
+
+ if (variable.type_name == "int") {
+ auto value = Debugger::the().session()->peek((u32*)variable_address);
+ ASSERT(value.has_value());
+ return String::formatted("{}", static_cast<int>(value.value()));
+ }
+
+ if (variable.type_name == "char") {
+ auto value = Debugger::the().session()->peek((u32*)variable_address);
+ ASSERT(value.has_value());
+ return String::formatted("'{0:c}' ({0:d})", value.value());
+ }
+
+ if (variable.type_name == "bool") {
+ auto value = Debugger::the().session()->peek((u32*)variable_address);
+ ASSERT(value.has_value());
+ return (value.value() & 1) ? "true" : "false";
+ }
+
+ return String::formatted("type: {} @ {:p}, ", variable.type_name, variable_address);
+}
+
+static Optional<u32> string_to_variable_value(const StringView& string_value, const Debug::DebugInfo::VariableInfo& variable)
+{
+ if (variable.is_enum_type()) {
+ auto prefix_string = String::formatted("{}::", variable.type_name);
+ auto string_to_use = string_value;
+ if (string_value.starts_with(prefix_string))
+ string_to_use = string_value.substring_view(prefix_string.length(), string_value.length() - prefix_string.length());
+
+ auto it = variable.type->members.find_if([string_to_use](const auto& enumerator) {
+ return enumerator->name == string_to_use;
+ });
+
+ if (it.is_end())
+ return {};
+ return (*it)->constant_data.as_u32;
+ }
+
+ if (variable.type_name == "int") {
+ auto value = string_value.to_int();
+ if (value.has_value())
+ return value.value();
+ return {};
+ }
+
+ if (variable.type_name == "bool") {
+ if (string_value == "true")
+ return true;
+ if (string_value == "false")
+ return false;
+ return {};
+ }
+
+ return {};
+}
+
+void VariablesModel::set_variable_value(const GUI::ModelIndex& index, const StringView& string_value, GUI::Window* parent_window)
+{
+ auto variable = static_cast<const Debug::DebugInfo::VariableInfo*>(index.internal_data());
+
+ auto value = string_to_variable_value(string_value, *variable);
+
+ if (value.has_value()) {
+ auto success = Debugger::the().session()->poke((u32*)variable->location_data.address, value.value());
+ ASSERT(success);
+ return;
+ }
+
+ GUI::MessageBox::show(
+ parent_window,
+ String::formatted("String value \"{}\" could not be converted to a value of type {}.", string_value, variable->type_name),
+ "Set value failed",
+ GUI::MessageBox::Type::Error);
+}
+
+GUI::Variant VariablesModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto* variable = static_cast<const Debug::DebugInfo::VariableInfo*>(index.internal_data());
+ switch (role) {
+ case GUI::ModelRole::Display: {
+ auto value_as_string = variable_value_as_string(*variable);
+ return String::formatted("{}: {}", variable->name, value_as_string);
+ }
+ case GUI::ModelRole::Icon:
+ return m_variable_icon;
+ default:
+ return {};
+ }
+}
+
+void VariablesModel::update()
+{
+ did_update();
+}
+
+RefPtr<VariablesModel> VariablesModel::create(const PtraceRegisters& regs)
+{
+ auto lib = Debugger::the().session()->library_at(regs.eip);
+ if (!lib)
+ return nullptr;
+ auto variables = lib->debug_info->get_variables_in_current_scope(regs);
+ return adopt(*new VariablesModel(move(variables), regs));
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/Debugger/VariablesModel.h b/Userland/DevTools/HackStudio/Debugger/VariablesModel.h
new file mode 100644
index 0000000000..41ed5e3ec9
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Debugger/VariablesModel.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Debugger.h"
+#include <AK/NonnullOwnPtr.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/TreeView.h>
+#include <sys/arch/i386/regs.h>
+
+namespace HackStudio {
+
+class VariablesModel final : public GUI::Model {
+public:
+ static RefPtr<VariablesModel> create(const PtraceRegisters& regs);
+
+ void set_variable_value(const GUI::ModelIndex&, const StringView&, GUI::Window*);
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return 1; }
+ virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override;
+ virtual void update() override;
+ virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override;
+ virtual GUI::ModelIndex index(int row, int column = 0, const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+
+private:
+ explicit VariablesModel(NonnullOwnPtrVector<Debug::DebugInfo::VariableInfo>&& variables, const PtraceRegisters& regs)
+ : m_variables(move(variables))
+ , m_regs(regs)
+ {
+ m_variable_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png"));
+ }
+ NonnullOwnPtrVector<Debug::DebugInfo::VariableInfo> m_variables;
+ PtraceRegisters m_regs;
+
+ GUI::Icon m_variable_icon;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Editor.cpp b/Userland/DevTools/HackStudio/Editor.cpp
new file mode 100644
index 0000000000..3c2d3b636b
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Editor.cpp
@@ -0,0 +1,530 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Editor.h"
+#include "Debugger/Debugger.h"
+#include "EditorWrapper.h"
+#include "HackStudio.h"
+#include "Language.h"
+#include <AK/ByteBuffer.h>
+#include <AK/LexicalPath.h>
+#include <LibCore/DirIterator.h>
+#include <LibCore/File.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/CppSyntaxHighlighter.h>
+#include <LibGUI/GMLSyntaxHighlighter.h>
+#include <LibGUI/INISyntaxHighlighter.h>
+#include <LibGUI/JSSyntaxHighlighter.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/ShellSyntaxHighlighter.h>
+#include <LibGUI/SyntaxHighlighter.h>
+#include <LibGUI/Window.h>
+#include <LibMarkdown/Document.h>
+#include <LibWeb/DOM/ElementFactory.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/HTML/HTMLHeadElement.h>
+#include <LibWeb/OutOfProcessWebView.h>
+#include <fcntl.h>
+
+// #define EDITOR_DEBUG
+
+namespace HackStudio {
+
+Editor::Editor()
+{
+ set_document(CodeDocument::create());
+ m_documentation_tooltip_window = GUI::Window::construct();
+ m_documentation_tooltip_window->set_rect(0, 0, 500, 400);
+ m_documentation_tooltip_window->set_window_type(GUI::WindowType::Tooltip);
+ m_documentation_page_view = m_documentation_tooltip_window->set_main_widget<Web::OutOfProcessWebView>();
+}
+
+Editor::~Editor()
+{
+}
+
+EditorWrapper& Editor::wrapper()
+{
+ return static_cast<EditorWrapper&>(*parent());
+}
+const EditorWrapper& Editor::wrapper() const
+{
+ return static_cast<const EditorWrapper&>(*parent());
+}
+
+void Editor::focusin_event(GUI::FocusEvent& event)
+{
+ wrapper().set_editor_has_focus({}, true);
+ if (on_focus)
+ on_focus();
+ GUI::TextEditor::focusin_event(event);
+}
+
+void Editor::focusout_event(GUI::FocusEvent& event)
+{
+ wrapper().set_editor_has_focus({}, false);
+ GUI::TextEditor::focusout_event(event);
+}
+
+Gfx::IntRect Editor::breakpoint_icon_rect(size_t line_number) const
+{
+ auto ruler_line_rect = ruler_content_rect(line_number);
+
+ auto scroll_value = vertical_scrollbar().value();
+ ruler_line_rect = ruler_line_rect.translated({ 0, -scroll_value });
+ auto center = ruler_line_rect.center().translated({ ruler_line_rect.width() - 10, -line_spacing() - 3 });
+ constexpr int size = 32;
+ return { center.x() - size / 2, center.y() - size / 2, size, size };
+}
+
+void Editor::paint_event(GUI::PaintEvent& event)
+{
+ GUI::TextEditor::paint_event(event);
+
+ GUI::Painter painter(*this);
+ if (is_focused()) {
+ painter.add_clip_rect(event.rect());
+
+ auto rect = frame_inner_rect();
+ if (vertical_scrollbar().is_visible())
+ rect.set_width(rect.width() - vertical_scrollbar().width());
+ if (horizontal_scrollbar().is_visible())
+ rect.set_height(rect.height() - horizontal_scrollbar().height());
+ painter.draw_rect(rect, palette().selection());
+ }
+
+ if (ruler_visible()) {
+ size_t first_visible_line = text_position_at(event.rect().top_left()).line();
+ size_t last_visible_line = text_position_at(event.rect().bottom_right()).line();
+ for (size_t line : breakpoint_lines()) {
+ if (line < first_visible_line || line > last_visible_line) {
+ continue;
+ }
+ const auto& icon = breakpoint_icon_bitmap();
+ painter.blit(breakpoint_icon_rect(line).center(), icon, icon.rect());
+ }
+ if (execution_position().has_value()) {
+ const auto& icon = current_position_icon_bitmap();
+ painter.blit(breakpoint_icon_rect(execution_position().value()).center(), icon, icon.rect());
+ }
+ }
+}
+
+static HashMap<String, String>& man_paths()
+{
+ static HashMap<String, String> paths;
+ if (paths.is_empty()) {
+ // FIXME: This should also search man3, possibly other places..
+ Core::DirIterator it("/usr/share/man/man2", Core::DirIterator::Flags::SkipDots);
+ while (it.has_next()) {
+ auto path = String::formatted("/usr/share/man/man2/{}", it.next_path());
+ auto title = LexicalPath(path).title();
+ paths.set(title, path);
+ }
+ }
+
+ return paths;
+}
+
+void Editor::show_documentation_tooltip_if_available(const String& hovered_token, const Gfx::IntPoint& screen_location)
+{
+ auto it = man_paths().find(hovered_token);
+ if (it == man_paths().end()) {
+#ifdef EDITOR_DEBUG
+ dbgln("no man path for {}", hovered_token);
+#endif
+ m_documentation_tooltip_window->hide();
+ return;
+ }
+
+ if (m_documentation_tooltip_window->is_visible() && hovered_token == m_last_parsed_token) {
+ return;
+ }
+
+#ifdef EDITOR_DEBUG
+ dbgln("opening {}", it->value);
+#endif
+ auto file = Core::File::construct(it->value);
+ if (!file->open(Core::File::ReadOnly)) {
+ dbgln("failed to open {}, {}", it->value, file->error_string());
+ return;
+ }
+
+ auto man_document = Markdown::Document::parse(file->read_all());
+
+ if (!man_document) {
+ dbgln("failed to parse markdown");
+ return;
+ }
+
+ StringBuilder html;
+ // FIXME: With the InProcessWebView we used to manipulate the document body directly,
+ // With OutOfProcessWebView this isn't possible (at the moment). The ideal solution
+ // is probably to tweak Markdown::Document::render_to_html() so we can inject styles
+ // into the rendered HTML easily.
+ html.append(man_document->render_to_html());
+ html.append("<style>body { background-color: #dac7b5; }</style>");
+ m_documentation_page_view->load_html(html.build(), {});
+
+ m_documentation_tooltip_window->move_to(screen_location.translated(4, 4));
+ m_documentation_tooltip_window->show();
+
+ m_last_parsed_token = hovered_token;
+}
+
+void Editor::mousemove_event(GUI::MouseEvent& event)
+{
+ GUI::TextEditor::mousemove_event(event);
+
+ if (document().spans().is_empty())
+ return;
+
+ auto text_position = text_position_at(event.position());
+ if (!text_position.is_valid()) {
+ m_documentation_tooltip_window->hide();
+ return;
+ }
+
+ auto highlighter = wrapper().editor().syntax_highlighter();
+ if (!highlighter)
+ return;
+
+ bool hide_tooltip = true;
+ bool is_over_link = false;
+
+ auto ruler_line_rect = ruler_content_rect(text_position.line());
+ auto hovering_lines_ruler = (event.position().x() < ruler_line_rect.width());
+ if (hovering_lines_ruler && !is_in_drag_select())
+ set_override_cursor(Gfx::StandardCursor::Arrow);
+ else if (m_hovering_editor)
+ set_override_cursor(m_hovering_link && event.ctrl() ? Gfx::StandardCursor::Hand : Gfx::StandardCursor::IBeam);
+
+ for (auto& span : document().spans()) {
+ if (span.range.contains(m_previous_text_position) && !span.range.contains(text_position)) {
+ if (highlighter->is_navigatable(span.data) && span.attributes.underline) {
+ span.attributes.underline = false;
+ wrapper().editor().update();
+ }
+ }
+
+ if (span.range.contains(text_position)) {
+ auto adjusted_range = span.range;
+ auto end_line_length = document().line(span.range.end().line()).length();
+ adjusted_range.end().set_column(min(end_line_length, adjusted_range.end().column() + 1));
+ auto hovered_span_text = document().text_in_range(adjusted_range);
+#ifdef EDITOR_DEBUG
+ dbgln("Hovering: {} \"{}\"", adjusted_range, hovered_span_text);
+#endif
+
+ if (highlighter->is_navigatable(span.data)) {
+ is_over_link = true;
+ bool was_underlined = span.attributes.underline;
+ span.attributes.underline = event.modifiers() & Mod_Ctrl;
+ if (span.attributes.underline != was_underlined) {
+ wrapper().editor().update();
+ }
+ }
+ if (highlighter->is_identifier(span.data)) {
+ show_documentation_tooltip_if_available(hovered_span_text, event.position().translated(screen_relative_rect().location()));
+ hide_tooltip = false;
+ }
+ }
+ }
+
+ m_previous_text_position = text_position;
+ if (hide_tooltip)
+ m_documentation_tooltip_window->hide();
+
+ m_hovering_link = is_over_link && (event.modifiers() & Mod_Ctrl);
+}
+
+void Editor::mousedown_event(GUI::MouseEvent& event)
+{
+ auto highlighter = wrapper().editor().syntax_highlighter();
+ if (!highlighter) {
+ GUI::TextEditor::mousedown_event(event);
+ return;
+ }
+
+ auto text_position = text_position_at(event.position());
+ auto ruler_line_rect = ruler_content_rect(text_position.line());
+ if (event.button() == GUI::MouseButton::Left && event.position().x() < ruler_line_rect.width()) {
+ if (!breakpoint_lines().contains_slow(text_position.line())) {
+ breakpoint_lines().append(text_position.line());
+ Debugger::on_breakpoint_change(wrapper().filename_label().text(), text_position.line(), BreakpointChange::Added);
+ } else {
+ breakpoint_lines().remove_first_matching([&](size_t line) { return line == text_position.line(); });
+ Debugger::on_breakpoint_change(wrapper().filename_label().text(), text_position.line(), BreakpointChange::Removed);
+ }
+ }
+
+ if (!(event.modifiers() & Mod_Ctrl)) {
+ GUI::TextEditor::mousedown_event(event);
+ return;
+ }
+
+ if (!text_position.is_valid()) {
+ GUI::TextEditor::mousedown_event(event);
+ return;
+ }
+
+ if (auto* span = document().span_at(text_position)) {
+ if (!highlighter->is_navigatable(span->data)) {
+ GUI::TextEditor::mousedown_event(event);
+ return;
+ }
+
+ auto adjusted_range = span->range;
+ adjusted_range.end().set_column(adjusted_range.end().column() + 1);
+ auto span_text = document().text_in_range(adjusted_range);
+ auto header_path = span_text.substring(1, span_text.length() - 2);
+#ifdef EDITOR_DEBUG
+ dbgln("Ctrl+click: {} \"{}\"", adjusted_range, header_path);
+#endif
+ navigate_to_include_if_available(header_path);
+ return;
+ }
+
+ GUI::TextEditor::mousedown_event(event);
+}
+
+void Editor::enter_event(Core::Event& event)
+{
+ m_hovering_editor = true;
+ GUI::TextEditor::enter_event(event);
+}
+
+void Editor::leave_event(Core::Event& event)
+{
+ m_hovering_editor = false;
+ GUI::TextEditor::leave_event(event);
+}
+
+static HashMap<String, String>& include_paths()
+{
+ static HashMap<String, String> paths;
+
+ auto add_directory = [](String base, Optional<String> recursive, auto handle_directory) -> void {
+ Core::DirIterator it(recursive.value_or(base), Core::DirIterator::Flags::SkipDots);
+ while (it.has_next()) {
+ auto path = it.next_full_path();
+ if (!Core::File::is_directory(path)) {
+ auto key = path.substring(base.length() + 1, path.length() - base.length() - 1);
+#ifdef EDITOR_DEBUG
+ dbgln("Adding header \"{}\" in path \"{}\"", key, path);
+#endif
+ paths.set(key, path);
+ } else {
+ handle_directory(base, path, handle_directory);
+ }
+ }
+ };
+
+ if (paths.is_empty()) {
+ add_directory(".", {}, add_directory);
+ add_directory("/usr/local/include", {}, add_directory);
+ add_directory("/usr/local/include/c++/9.2.0", {}, add_directory);
+ add_directory("/usr/include", {}, add_directory);
+ }
+
+ return paths;
+}
+
+void Editor::navigate_to_include_if_available(String path)
+{
+ auto it = include_paths().find(path);
+ if (it == include_paths().end()) {
+#ifdef EDITOR_DEBUG
+ dbgln("no header {} found.", path);
+#endif
+ return;
+ }
+
+ on_open(it->value);
+}
+
+void Editor::set_execution_position(size_t line_number)
+{
+ code_document().set_execution_position(line_number);
+ scroll_position_into_view({ line_number, 0 });
+ update(breakpoint_icon_rect(line_number));
+}
+
+void Editor::clear_execution_position()
+{
+ if (!execution_position().has_value()) {
+ return;
+ }
+ size_t previous_position = execution_position().value();
+ code_document().clear_execution_position();
+ update(breakpoint_icon_rect(previous_position));
+}
+
+const Gfx::Bitmap& Editor::breakpoint_icon_bitmap()
+{
+ static auto bitmap = Gfx::Bitmap::load_from_file("/res/icons/16x16/breakpoint.png");
+ return *bitmap;
+}
+
+const Gfx::Bitmap& Editor::current_position_icon_bitmap()
+{
+ static auto bitmap = Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png");
+ return *bitmap;
+}
+
+const CodeDocument& Editor::code_document() const
+{
+ const auto& doc = document();
+ ASSERT(doc.is_code_document());
+ return static_cast<const CodeDocument&>(doc);
+}
+
+CodeDocument& Editor::code_document()
+{
+ return const_cast<CodeDocument&>(static_cast<const Editor&>(*this).code_document());
+}
+
+void Editor::set_document(GUI::TextDocument& doc)
+{
+ ASSERT(doc.is_code_document());
+ GUI::TextEditor::set_document(doc);
+
+ set_override_cursor(Gfx::StandardCursor::IBeam);
+
+ CodeDocument& code_document = static_cast<CodeDocument&>(doc);
+ switch (code_document.language()) {
+ case Language::Cpp:
+ set_syntax_highlighter(make<GUI::CppSyntaxHighlighter>());
+ m_language_client = get_language_client<LanguageClients::Cpp::ServerConnection>(project().root_path());
+ break;
+ case Language::GML:
+ set_syntax_highlighter(make<GUI::GMLSyntaxHighlighter>());
+ break;
+ case Language::JavaScript:
+ set_syntax_highlighter(make<GUI::JSSyntaxHighlighter>());
+ break;
+ case Language::Ini:
+ set_syntax_highlighter(make<GUI::IniSyntaxHighlighter>());
+ break;
+ case Language::Shell:
+ set_syntax_highlighter(make<GUI::ShellSyntaxHighlighter>());
+ m_language_client = get_language_client<LanguageClients::Shell::ServerConnection>(project().root_path());
+ break;
+ default:
+ set_syntax_highlighter({});
+ }
+
+ if (m_language_client) {
+ set_autocomplete_provider(make<LanguageServerAidedAutocompleteProvider>(*m_language_client));
+ dbgln("Opening {}", code_document.file_path());
+ int fd = open(code_document.file_path().characters(), O_RDONLY | O_NOCTTY);
+ if (fd < 0) {
+ perror("open");
+ return;
+ }
+ m_language_client->open_file(code_document.file_path(), fd);
+ close(fd);
+ }
+}
+
+Optional<Editor::AutoCompleteRequestData> Editor::get_autocomplete_request_data()
+{
+ if (!wrapper().editor().m_language_client)
+ return {};
+
+ return Editor::AutoCompleteRequestData { cursor() };
+}
+
+void Editor::LanguageServerAidedAutocompleteProvider::provide_completions(Function<void(Vector<Entry>)> callback)
+{
+ auto& editor = static_cast<Editor&>(*m_editor).wrapper().editor();
+ auto data = editor.get_autocomplete_request_data();
+ if (!data.has_value())
+ callback({});
+
+ m_language_client.on_autocomplete_suggestions = [callback = move(callback)](auto suggestions) {
+ callback(suggestions);
+ };
+
+ m_language_client.request_autocomplete(
+ editor.code_document().file_path(),
+ data.value().position.line(),
+ data.value().position.column());
+}
+
+void Editor::on_edit_action(const GUI::Command& command)
+{
+ if (!m_language_client)
+ return;
+
+ if (command.is_insert_text()) {
+ const GUI::InsertTextCommand& insert_command = static_cast<const GUI::InsertTextCommand&>(command);
+ m_language_client->insert_text(
+ code_document().file_path(),
+ insert_command.text(),
+ insert_command.range().start().line(),
+ insert_command.range().start().column());
+ return;
+ }
+
+ if (command.is_remove_text()) {
+ const GUI::RemoveTextCommand& remove_command = static_cast<const GUI::RemoveTextCommand&>(command);
+ m_language_client->remove_text(
+ code_document().file_path(),
+ remove_command.range().start().line(),
+ remove_command.range().start().column(),
+ remove_command.range().end().line(),
+ remove_command.range().end().column());
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+void Editor::undo()
+{
+ TextEditor::undo();
+ flush_file_content_to_langauge_server();
+}
+
+void Editor::redo()
+{
+ TextEditor::redo();
+ flush_file_content_to_langauge_server();
+}
+
+void Editor::flush_file_content_to_langauge_server()
+{
+ if (!m_language_client)
+ return;
+
+ m_language_client->set_file_content(
+ code_document().file_path(),
+ document().text());
+}
+}
diff --git a/Userland/DevTools/HackStudio/Editor.h b/Userland/DevTools/HackStudio/Editor.h
new file mode 100644
index 0000000000..10c83a59d0
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Editor.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "CodeDocument.h"
+#include "Debugger/BreakpointCallback.h"
+#include "LanguageClient.h"
+#include <AK/Optional.h>
+#include <AK/OwnPtr.h>
+#include <LibGUI/TextEditor.h>
+#include <LibWeb/Forward.h>
+
+namespace HackStudio {
+
+class EditorWrapper;
+
+class Editor final : public GUI::TextEditor {
+ C_OBJECT(Editor)
+public:
+ virtual ~Editor() override;
+
+ Function<void()> on_focus;
+ Function<void(String)> on_open;
+
+ EditorWrapper& wrapper();
+ const EditorWrapper& wrapper() const;
+
+ const Vector<size_t>& breakpoint_lines() const { return code_document().breakpoint_lines(); }
+ Vector<size_t>& breakpoint_lines() { return code_document().breakpoint_lines(); }
+ Optional<size_t> execution_position() const { return code_document().execution_position(); }
+ void set_execution_position(size_t line_number);
+ void clear_execution_position();
+
+ const CodeDocument& code_document() const;
+ CodeDocument& code_document();
+
+ virtual void set_document(GUI::TextDocument&) override;
+
+ virtual void on_edit_action(const GUI::Command&) override;
+
+ virtual void undo() override;
+ virtual void redo() override;
+
+private:
+ virtual void focusin_event(GUI::FocusEvent&) override;
+ virtual void focusout_event(GUI::FocusEvent&) override;
+ virtual void paint_event(GUI::PaintEvent&) override;
+ virtual void mousemove_event(GUI::MouseEvent&) override;
+ virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual void enter_event(Core::Event&) override;
+ virtual void leave_event(Core::Event&) override;
+
+ void show_documentation_tooltip_if_available(const String&, const Gfx::IntPoint& screen_location);
+ void navigate_to_include_if_available(String);
+
+ Gfx::IntRect breakpoint_icon_rect(size_t line_number) const;
+ static const Gfx::Bitmap& breakpoint_icon_bitmap();
+ static const Gfx::Bitmap& current_position_icon_bitmap();
+
+ struct AutoCompleteRequestData {
+ GUI::TextPosition position;
+ };
+
+ class LanguageServerAidedAutocompleteProvider final : virtual public GUI::AutocompleteProvider {
+ public:
+ LanguageServerAidedAutocompleteProvider(LanguageClient& client)
+ : m_language_client(client)
+ {
+ }
+ virtual ~LanguageServerAidedAutocompleteProvider() override { }
+
+ private:
+ virtual void provide_completions(Function<void(Vector<Entry>)> callback) override;
+ LanguageClient& m_language_client;
+ };
+
+ Optional<AutoCompleteRequestData> get_autocomplete_request_data();
+
+ void flush_file_content_to_langauge_server();
+
+ explicit Editor();
+
+ RefPtr<GUI::Window> m_documentation_tooltip_window;
+ RefPtr<Web::OutOfProcessWebView> m_documentation_page_view;
+ String m_last_parsed_token;
+ GUI::TextPosition m_previous_text_position { 0, 0 };
+ bool m_hovering_editor { false };
+ bool m_hovering_link { false };
+ bool m_autocomplete_in_focus { false };
+
+ OwnPtr<LanguageClient> m_language_client;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/EditorWrapper.cpp b/Userland/DevTools/HackStudio/EditorWrapper.cpp
new file mode 100644
index 0000000000..e4b6a346c7
--- /dev/null
+++ b/Userland/DevTools/HackStudio/EditorWrapper.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "EditorWrapper.h"
+#include "Editor.h"
+#include "HackStudio.h"
+#include <LibGUI/Action.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/InputBox.h>
+#include <LibGUI/Label.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+
+namespace HackStudio {
+
+EditorWrapper::EditorWrapper()
+{
+ set_layout<GUI::VerticalBoxLayout>();
+
+ auto& label_wrapper = add<GUI::Widget>();
+ label_wrapper.set_fixed_height(14);
+ label_wrapper.set_fill_with_background_color(true);
+ label_wrapper.set_layout<GUI::HorizontalBoxLayout>();
+ label_wrapper.layout()->set_margins({ 2, 0, 2, 0 });
+
+ m_filename_label = label_wrapper.add<GUI::Label>("(Untitled)");
+ m_filename_label->set_text_alignment(Gfx::TextAlignment::CenterLeft);
+ m_filename_label->set_fixed_height(14);
+
+ m_cursor_label = label_wrapper.add<GUI::Label>("(Cursor)");
+ m_cursor_label->set_text_alignment(Gfx::TextAlignment::CenterRight);
+ m_cursor_label->set_fixed_height(14);
+
+ m_editor = add<Editor>();
+ m_editor->set_ruler_visible(true);
+ m_editor->set_line_wrapping_enabled(true);
+ m_editor->set_automatic_indentation_enabled(true);
+
+ m_editor->on_cursor_change = [this] {
+ m_cursor_label->set_text(String::formatted("Line: {}, Column: {}", m_editor->cursor().line() + 1, m_editor->cursor().column()));
+ };
+
+ m_editor->on_focus = [this] {
+ set_current_editor_wrapper(this);
+ };
+
+ m_editor->on_open = [](String path) {
+ open_file(path);
+ };
+}
+
+EditorWrapper::~EditorWrapper()
+{
+}
+
+void EditorWrapper::set_editor_has_focus(Badge<Editor>, bool focus)
+{
+ m_filename_label->set_font(focus ? Gfx::FontDatabase::default_bold_font() : Gfx::FontDatabase::default_font());
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/EditorWrapper.h b/Userland/DevTools/HackStudio/EditorWrapper.h
new file mode 100644
index 0000000000..0cf67032a2
--- /dev/null
+++ b/Userland/DevTools/HackStudio/EditorWrapper.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Debugger/BreakpointCallback.h"
+#include <AK/Function.h>
+#include <AK/Vector.h>
+#include <LibGUI/Widget.h>
+#include <string.h>
+
+namespace HackStudio {
+
+class Editor;
+
+class EditorWrapper : public GUI::Widget {
+ C_OBJECT(EditorWrapper)
+public:
+ virtual ~EditorWrapper() override;
+
+ Editor& editor() { return *m_editor; }
+ const Editor& editor() const { return *m_editor; }
+
+ GUI::Label& filename_label() { return *m_filename_label; }
+ const GUI::Label& filename_label() const { return *m_filename_label; }
+
+ void set_editor_has_focus(Badge<Editor>, bool);
+
+private:
+ EditorWrapper();
+
+ RefPtr<GUI::Label> m_filename_label;
+ RefPtr<GUI::Label> m_cursor_label;
+ RefPtr<Editor> m_editor;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/FindInFilesWidget.cpp b/Userland/DevTools/HackStudio/FindInFilesWidget.cpp
new file mode 100644
index 0000000000..8347e73c81
--- /dev/null
+++ b/Userland/DevTools/HackStudio/FindInFilesWidget.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "FindInFilesWidget.h"
+#include "HackStudio.h"
+#include "Project.h"
+#include <AK/StringBuilder.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/TableView.h>
+#include <LibGUI/TextBox.h>
+#include <LibGfx/FontDatabase.h>
+
+namespace HackStudio {
+
+struct Match {
+ String filename;
+ GUI::TextRange range;
+ String text;
+};
+
+class SearchResultsModel final : public GUI::Model {
+public:
+ enum Column {
+ Filename,
+ Location,
+ MatchedText,
+ __Count
+ };
+
+ explicit SearchResultsModel(const Vector<Match>&& matches)
+ : m_matches(move(matches))
+ {
+ }
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_matches.size(); }
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Count; }
+
+ virtual String column_name(int column) const override
+ {
+ switch (column) {
+ case Column::Filename:
+ return "Filename";
+ case Column::Location:
+ return "#";
+ case Column::MatchedText:
+ return "Text";
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override
+ {
+ if (role == GUI::ModelRole::TextAlignment)
+ return Gfx::TextAlignment::CenterLeft;
+ if (role == GUI::ModelRole::Font) {
+ if (index.column() == Column::MatchedText)
+ return Gfx::FontDatabase::default_fixed_width_font();
+ return {};
+ }
+ if (role == GUI::ModelRole::Display) {
+ auto& match = m_matches.at(index.row());
+ switch (index.column()) {
+ case Column::Filename:
+ return match.filename;
+ case Column::Location:
+ return (int)match.range.start().line();
+ case Column::MatchedText:
+ return match.text;
+ }
+ }
+ return {};
+ }
+
+ virtual void update() override { }
+ virtual GUI::ModelIndex index(int row, int column = 0, const GUI::ModelIndex& = GUI::ModelIndex()) const override
+ {
+ if (row < 0 || row >= (int)m_matches.size())
+ return {};
+ if (column < 0 || column >= Column::__Count)
+ return {};
+ return create_index(row, column, &m_matches.at(row));
+ }
+
+private:
+ Vector<Match> m_matches;
+};
+
+static RefPtr<SearchResultsModel> find_in_files(const StringView& text)
+{
+ Vector<Match> matches;
+ project().for_each_text_file([&](auto& file) {
+ auto matches_in_file = file.document().find_all(text);
+ for (auto& range : matches_in_file) {
+ auto whole_line_range = file.document().range_for_entire_line(range.start().line());
+ auto whole_line_containing_match = file.document().text_in_range(whole_line_range);
+ auto left_part = whole_line_containing_match.substring(0, range.start().column());
+ auto right_part = whole_line_containing_match.substring(range.end().column(), whole_line_containing_match.length() - range.end().column());
+ StringBuilder builder;
+ builder.append(left_part);
+ builder.append(0x01);
+ builder.append(file.document().text_in_range(range));
+ builder.append(0x02);
+ builder.append(right_part);
+ matches.append({ file.name(), range, builder.to_string() });
+ }
+ });
+
+ return adopt(*new SearchResultsModel(move(matches)));
+}
+
+FindInFilesWidget::FindInFilesWidget()
+{
+ set_layout<GUI::VerticalBoxLayout>();
+
+ auto& top_container = add<Widget>();
+ top_container.set_layout<GUI::HorizontalBoxLayout>();
+ top_container.set_fixed_height(20);
+
+ m_textbox = top_container.add<GUI::TextBox>();
+
+ m_button = top_container.add<GUI::Button>("Find in files");
+ m_button->set_fixed_width(100);
+
+ m_result_view = add<GUI::TableView>();
+
+ m_result_view->on_activation = [](auto& index) {
+ auto& match = *(const Match*)index.internal_data();
+ open_file(match.filename);
+ current_editor().set_selection(match.range);
+ current_editor().set_focus(true);
+ };
+
+ m_button->on_click = [this](auto) {
+ auto results_model = find_in_files(m_textbox->text());
+ m_result_view->set_model(results_model);
+ };
+ m_textbox->on_return_pressed = [this] {
+ m_button->click();
+ };
+}
+
+void FindInFilesWidget::focus_textbox_and_select_all()
+{
+ m_textbox->select_all();
+ m_textbox->set_focus(true);
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/FindInFilesWidget.h b/Userland/DevTools/HackStudio/FindInFilesWidget.h
new file mode 100644
index 0000000000..759d116374
--- /dev/null
+++ b/Userland/DevTools/HackStudio/FindInFilesWidget.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Button.h>
+#include <LibGUI/TextBox.h>
+#include <LibGUI/Widget.h>
+
+namespace HackStudio {
+
+class FindInFilesWidget final : public GUI::Widget {
+ C_OBJECT(FindInFilesWidget)
+public:
+ virtual ~FindInFilesWidget() override { }
+
+ void focus_textbox_and_select_all();
+
+private:
+ explicit FindInFilesWidget();
+
+ RefPtr<GUI::TextBox> m_textbox;
+ RefPtr<GUI::Button> m_button;
+ RefPtr<GUI::TableView> m_result_view;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/FormEditorWidget.cpp b/Userland/DevTools/HackStudio/FormEditorWidget.cpp
new file mode 100644
index 0000000000..9e9773d2c7
--- /dev/null
+++ b/Userland/DevTools/HackStudio/FormEditorWidget.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "FormEditorWidget.h"
+#include "CursorTool.h"
+#include "FormWidget.h"
+#include "WidgetTreeModel.h"
+#include <LibGUI/Painter.h>
+
+namespace HackStudio {
+
+FormEditorWidget::FormEditorWidget()
+ : m_tool(make<CursorTool>(*this))
+{
+ set_fill_with_background_color(true);
+
+ m_form_widget = add<FormWidget>();
+ m_widget_tree_model = WidgetTreeModel::create(*m_form_widget);
+}
+
+FormEditorWidget::~FormEditorWidget()
+{
+}
+
+void FormEditorWidget::paint_event(GUI::PaintEvent& event)
+{
+ GUI::Frame::paint_event(event);
+
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+}
+
+void FormEditorWidget::set_tool(NonnullOwnPtr<Tool> tool)
+{
+ m_tool->detach();
+ m_tool = move(tool);
+ m_tool->attach();
+}
+
+WidgetTreeModel& FormEditorWidget::model()
+{
+ return *m_widget_tree_model;
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/FormEditorWidget.h b/Userland/DevTools/HackStudio/FormEditorWidget.h
new file mode 100644
index 0000000000..a913eed01e
--- /dev/null
+++ b/Userland/DevTools/HackStudio/FormEditorWidget.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashTable.h>
+#include <LibGUI/ScrollableWidget.h>
+
+namespace HackStudio {
+
+class FormWidget;
+class Tool;
+class WidgetTreeModel;
+
+class FormEditorWidget final : public GUI::ScrollableWidget {
+ C_OBJECT(FormEditorWidget)
+public:
+ virtual ~FormEditorWidget() override;
+
+ FormWidget& form_widget() { return *m_form_widget; }
+ const FormWidget& form_widget() const { return *m_form_widget; }
+
+ Tool& tool() { return *m_tool; }
+ const Tool& tool() const { return *m_tool; }
+
+ void set_tool(NonnullOwnPtr<Tool>);
+
+ WidgetTreeModel& model();
+
+ class WidgetSelection {
+ public:
+ Function<void(GUI::Widget&)> on_remove;
+ Function<void(GUI::Widget&)> on_add;
+ Function<void()> on_clear;
+
+ void enable_hooks() { m_hooks_enabled = true; }
+ void disable_hooks() { m_hooks_enabled = false; }
+
+ bool is_empty() const
+ {
+ return m_widgets.is_empty();
+ }
+
+ bool contains(GUI::Widget& widget) const
+ {
+ return m_widgets.contains(&widget);
+ }
+
+ void toggle(GUI::Widget& widget)
+ {
+ if (contains(widget))
+ remove(widget);
+ else
+ add(widget);
+ }
+
+ void set(GUI::Widget& widget)
+ {
+ clear();
+ add(widget);
+ }
+
+ void remove(GUI::Widget& widget)
+ {
+ ASSERT(m_widgets.contains(&widget));
+ m_widgets.remove(&widget);
+ if (m_hooks_enabled && on_remove)
+ on_remove(widget);
+ }
+
+ void add(GUI::Widget& widget)
+ {
+ m_widgets.set(&widget);
+ if (m_hooks_enabled && on_add)
+ on_add(widget);
+ }
+
+ void clear()
+ {
+ m_widgets.clear();
+ if (m_hooks_enabled && on_clear)
+ on_clear();
+ }
+
+ template<typename Callback>
+ void for_each(Callback callback)
+ {
+ for (auto& it : m_widgets) {
+ if (callback(*it) == IterationDecision::Break)
+ break;
+ }
+ }
+
+ WidgetSelection() { }
+
+ private:
+ HashTable<GUI::Widget*> m_widgets;
+ bool m_hooks_enabled { true };
+ };
+
+ WidgetSelection& selection() { return m_selection; }
+
+private:
+ virtual void paint_event(GUI::PaintEvent&) override;
+
+ FormEditorWidget();
+
+ RefPtr<FormWidget> m_form_widget;
+ RefPtr<WidgetTreeModel> m_widget_tree_model;
+ NonnullOwnPtr<Tool> m_tool;
+ WidgetSelection m_selection;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/FormWidget.cpp b/Userland/DevTools/HackStudio/FormWidget.cpp
new file mode 100644
index 0000000000..a9924a6a2b
--- /dev/null
+++ b/Userland/DevTools/HackStudio/FormWidget.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "FormWidget.h"
+#include "FormEditorWidget.h"
+#include "Tool.h"
+#include <LibGUI/Painter.h>
+
+namespace HackStudio {
+
+FormWidget::FormWidget()
+{
+ set_focus_policy(GUI::FocusPolicy::StrongFocus);
+ set_fill_with_background_color(true);
+ set_relative_rect(5, 5, 400, 300);
+
+ set_greedy_for_hits(true);
+}
+
+FormWidget::~FormWidget()
+{
+}
+
+FormEditorWidget& FormWidget::editor()
+{
+ return static_cast<FormEditorWidget&>(*parent());
+}
+
+const FormEditorWidget& FormWidget::editor() const
+{
+ return static_cast<const FormEditorWidget&>(*parent());
+}
+
+void FormWidget::paint_event(GUI::PaintEvent& event)
+{
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ for (int y = 0; y < height(); y += m_grid_size) {
+ for (int x = 0; x < width(); x += m_grid_size) {
+ painter.set_pixel({ x, y }, Color::from_rgb(0x404040));
+ }
+ }
+}
+
+void FormWidget::second_paint_event(GUI::PaintEvent& event)
+{
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ if (!editor().selection().is_empty()) {
+ for_each_child_widget([&](auto& child) {
+ if (editor().selection().contains(child)) {
+ painter.draw_rect(child.relative_rect(), Color::Blue);
+ }
+ return IterationDecision::Continue;
+ });
+ }
+
+ editor().tool().on_second_paint(painter, event);
+}
+
+void FormWidget::mousedown_event(GUI::MouseEvent& event)
+{
+ editor().tool().on_mousedown(event);
+}
+
+void FormWidget::mouseup_event(GUI::MouseEvent& event)
+{
+ editor().tool().on_mouseup(event);
+}
+
+void FormWidget::mousemove_event(GUI::MouseEvent& event)
+{
+ editor().tool().on_mousemove(event);
+}
+
+void FormWidget::keydown_event(GUI::KeyEvent& event)
+{
+ editor().tool().on_keydown(event);
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/FormWidget.h b/Userland/DevTools/HackStudio/FormWidget.h
new file mode 100644
index 0000000000..32f10109f1
--- /dev/null
+++ b/Userland/DevTools/HackStudio/FormWidget.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Badge.h>
+#include <LibGUI/Widget.h>
+
+namespace HackStudio {
+
+class CursorTool;
+class FormEditorWidget;
+
+class FormWidget final : public GUI::Widget {
+ C_OBJECT(FormWidget)
+public:
+ virtual ~FormWidget() override;
+
+ FormEditorWidget& editor();
+ const FormEditorWidget& editor() const;
+
+ // FIXME: This should be an app-wide preference instead.
+ int grid_size() const { return m_grid_size; }
+
+private:
+ virtual void paint_event(GUI::PaintEvent&) override;
+ virtual void second_paint_event(GUI::PaintEvent&) override;
+ virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual void mouseup_event(GUI::MouseEvent&) override;
+ virtual void mousemove_event(GUI::MouseEvent&) override;
+ virtual void keydown_event(GUI::KeyEvent&) override;
+
+ FormWidget();
+
+ int m_grid_size { 5 };
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Git/DiffViewer.cpp b/Userland/DevTools/HackStudio/Git/DiffViewer.cpp
new file mode 100644
index 0000000000..9633ff6d1d
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Git/DiffViewer.cpp
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DiffViewer.h"
+#include <LibDiff/Hunks.h>
+#include <LibGUI/AbstractView.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/Palette.h>
+
+// #define DEBUG_DIFF
+
+namespace HackStudio {
+
+DiffViewer::~DiffViewer()
+{
+}
+
+void DiffViewer::paint_event(GUI::PaintEvent& event)
+{
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(widget_inner_rect());
+ painter.add_clip_rect(event.rect());
+ painter.fill_rect(event.rect(), palette().color(background_role()));
+ painter.translate(frame_thickness(), frame_thickness());
+ painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
+
+ // Why we need to translate here again? We've already translated the painter.
+ // Anyways, it paints correctly so I'll leave it like this.
+ painter.fill_rect_with_dither_pattern(
+ separator_rect().translated(horizontal_scrollbar().value(), vertical_scrollbar().value()),
+ Gfx::Color::LightGray,
+ Gfx::Color::White);
+
+ size_t y_offset = 10;
+ size_t current_original_line_index = 0;
+ for (const auto& hunk : m_hunks) {
+ for (size_t i = current_original_line_index; i < hunk.original_start_line; ++i) {
+ draw_line(painter, m_original_lines[i], y_offset, LinePosition::Both, LineType::Normal);
+ y_offset += line_height();
+ }
+ current_original_line_index = hunk.original_start_line + hunk.removed_lines.size();
+
+ size_t left_y_offset = y_offset;
+ for (const auto& removed_line : hunk.removed_lines) {
+ draw_line(painter, removed_line, left_y_offset, LinePosition::Left, LineType::Diff);
+ left_y_offset += line_height();
+ }
+ for (int i = 0; i < (int)hunk.added_lines.size() - (int)hunk.removed_lines.size(); ++i) {
+ draw_line(painter, "", left_y_offset, LinePosition::Left, LineType::Missing);
+ left_y_offset += line_height();
+ }
+
+ size_t right_y_offset = y_offset;
+ for (const auto& added_line : hunk.added_lines) {
+ draw_line(painter, added_line, right_y_offset, LinePosition::Right, LineType::Diff);
+ right_y_offset += line_height();
+ }
+ for (int i = 0; i < (int)hunk.removed_lines.size() - (int)hunk.added_lines.size(); ++i) {
+ draw_line(painter, "", right_y_offset, LinePosition::Right, LineType::Missing);
+ right_y_offset += line_height();
+ }
+
+ ASSERT(left_y_offset == right_y_offset);
+ y_offset = left_y_offset;
+ }
+ for (size_t i = current_original_line_index; i < m_original_lines.size(); ++i) {
+ draw_line(painter, m_original_lines[i], y_offset, LinePosition::Both, LineType::Normal);
+ y_offset += line_height();
+ }
+}
+
+void DiffViewer::draw_line(GUI::Painter& painter, const String& line, size_t y_offset, LinePosition line_position, LineType line_type)
+{
+ size_t line_width = font().width(line);
+
+ constexpr size_t padding = 10;
+ size_t left_side_x_offset = padding;
+ size_t right_side_x_offset = separator_rect().x() + padding;
+
+ // FIXME: Long lines will overflow out of their side of the diff view
+ Gfx::IntRect left_line_rect { (int)left_side_x_offset, (int)y_offset, (int)line_width, (int)line_height() };
+ Gfx::IntRect right_line_rect { (int)right_side_x_offset, (int)y_offset, (int)line_width, (int)line_height() };
+ auto color = palette().color(foreground_role());
+
+ if (line_position == LinePosition::Left || line_position == LinePosition::Both) {
+ painter.draw_text(left_line_rect, line, Gfx::TextAlignment::TopLeft, color);
+ if (line_type != LineType::Normal) {
+ Gfx::IntRect outline = { (int)left_side_x_offset, ((int)y_offset) - 2, separator_rect().x() - (int)(padding * 2), (int)line_height() };
+ if (line_type == LineType::Diff) {
+ painter.fill_rect(
+ outline,
+ red_background());
+ }
+ if (line_type == LineType::Missing) {
+ painter.fill_rect(
+ outline,
+ gray_background());
+ }
+ }
+ }
+ if (line_position == LinePosition::Right || line_position == LinePosition::Both) {
+ painter.draw_text(right_line_rect, line, Gfx::TextAlignment::TopLeft, color);
+ if (line_type != LineType::Normal) {
+ Gfx::IntRect outline = { (int)right_side_x_offset, ((int)y_offset) - 2, frame_inner_rect().width() - separator_rect().x() - (int)(padding * 2) - 10, (int)line_height() };
+ if (line_type == LineType::Diff) {
+ painter.fill_rect(
+ outline,
+ green_background());
+ }
+ if (line_type == LineType::Missing) {
+ painter.fill_rect(
+ outline,
+ gray_background());
+ }
+ }
+ }
+}
+
+size_t DiffViewer::line_height() const
+{
+ return font().glyph_height() + 4;
+}
+
+Gfx::IntRect DiffViewer::separator_rect() const
+{
+ return Gfx::IntRect { frame_inner_rect().width() / 2 - 2,
+ 0,
+ 4,
+ frame_inner_rect().height() };
+}
+
+void DiffViewer::set_content(const String& original, const String& diff)
+{
+ m_original_lines = split_to_lines(original);
+ m_hunks = Diff::parse_hunks(diff);
+
+#ifdef DEBUG_DIFF
+ for (size_t i = 0; i < m_original_lines.size(); ++i)
+ dbgln("{}:{}", i, m_original_lines[i]);
+#endif
+}
+
+DiffViewer::DiffViewer()
+{
+ setup_properties();
+}
+
+DiffViewer::DiffViewer(const String& original, const String& diff)
+ : m_original_lines(split_to_lines(original))
+ , m_hunks(Diff::parse_hunks(diff))
+{
+ setup_properties();
+}
+
+void DiffViewer::setup_properties()
+{
+ set_font(Gfx::FontDatabase::default_fixed_width_font());
+ set_background_role(ColorRole::Base);
+ set_foreground_role(ColorRole::BaseText);
+}
+
+Vector<String> DiffViewer::split_to_lines(const String& text)
+{
+ // NOTE: This is slightly different than text.split('\n')
+ Vector<String> lines;
+ size_t next_line_start_index = 0;
+ for (size_t i = 0; i < text.length(); ++i) {
+ if (text[i] == '\n') {
+ auto line_text = text.substring(next_line_start_index, i - next_line_start_index);
+ lines.append(move(line_text));
+ next_line_start_index = i + 1;
+ }
+ }
+ lines.append(text.substring(next_line_start_index, text.length() - next_line_start_index));
+ return lines;
+}
+
+Gfx::Color DiffViewer::red_background()
+{
+ static Gfx::Color color = Gfx::Color::from_rgba(0x88ff0000);
+ return color;
+}
+
+Gfx::Color DiffViewer::green_background()
+{
+ static Gfx::Color color = Gfx::Color::from_rgba(0x8800ff00);
+ return color;
+}
+
+Gfx::Color DiffViewer::gray_background()
+{
+ static Gfx::Color color = Gfx::Color::from_rgba(0x88888888);
+ return color;
+}
+
+void DiffViewer::update_content_size()
+{
+ if (m_hunks.is_empty()) {
+ set_content_size({ 0, 0 });
+ return;
+ }
+
+ size_t num_lines = 0;
+ size_t current_original_line_index = 0;
+ for (const auto& hunk : m_hunks) {
+ num_lines += ((int)hunk.original_start_line - (int)current_original_line_index);
+
+ num_lines += hunk.removed_lines.size();
+ if (hunk.added_lines.size() > hunk.removed_lines.size()) {
+ num_lines += ((int)hunk.added_lines.size() - (int)hunk.removed_lines.size());
+ }
+ current_original_line_index = hunk.original_start_line + hunk.removed_lines.size();
+ }
+ num_lines += ((int)m_original_lines.size() - (int)current_original_line_index);
+
+ // TODO: Support Horizontal scrolling
+ set_content_size({ 0, (int)(num_lines * line_height()) });
+}
+
+void DiffViewer::resize_event(GUI::ResizeEvent& event)
+{
+ ScrollableWidget::resize_event(event);
+ update_content_size();
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/Git/DiffViewer.h b/Userland/DevTools/HackStudio/Git/DiffViewer.h
new file mode 100644
index 0000000000..1cc293e63d
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Git/DiffViewer.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <LibDiff/Hunks.h>
+#include <LibGUI/ScrollableWidget.h>
+
+namespace HackStudio {
+class DiffViewer final : public GUI::ScrollableWidget {
+ C_OBJECT(DiffViewer)
+public:
+ virtual ~DiffViewer() override;
+
+ void set_content(const String& original, const String& diff);
+
+private:
+ DiffViewer(const String& original, const String& diff);
+ DiffViewer();
+
+ void setup_properties();
+
+ virtual void paint_event(GUI::PaintEvent&) override;
+ virtual void resize_event(GUI::ResizeEvent&) override;
+
+ void update_content_size();
+
+ enum class LinePosition {
+ Left,
+ Right,
+ Both,
+ };
+
+ enum class LineType {
+ Normal,
+ Diff,
+ Missing,
+ };
+
+ void draw_line(GUI::Painter&, const String& line, size_t y_offset, LinePosition, LineType);
+
+ static Vector<String> split_to_lines(const String& text);
+
+ static Gfx::Color red_background();
+ static Gfx::Color green_background();
+ static Gfx::Color gray_background();
+
+ size_t line_height() const;
+
+ Gfx::IntRect separator_rect() const;
+
+ Vector<String> m_original_lines;
+ Vector<Diff::Hunk> m_hunks;
+};
+}
diff --git a/Userland/DevTools/HackStudio/Git/GitFilesModel.cpp b/Userland/DevTools/HackStudio/Git/GitFilesModel.cpp
new file mode 100644
index 0000000000..720198586a
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Git/GitFilesModel.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "GitFilesModel.h"
+
+namespace HackStudio {
+
+NonnullRefPtr<GitFilesModel> GitFilesModel::create(Vector<LexicalPath>&& files)
+{
+ return adopt(*new GitFilesModel(move(files)));
+}
+
+GitFilesModel::GitFilesModel(Vector<LexicalPath>&& files)
+ : m_files(move(files))
+{
+}
+
+GUI::Variant GitFilesModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ if (role == GUI::ModelRole::Display) {
+ return m_files.at(index.row()).string();
+ }
+ return {};
+}
+
+GUI::ModelIndex GitFilesModel::index(int row, int column, const GUI::ModelIndex&) const
+{
+ if (row < 0 || row >= static_cast<int>(m_files.size()))
+ return {};
+ return create_index(row, column, &m_files.at(row));
+}
+
+};
diff --git a/Userland/DevTools/HackStudio/Git/GitFilesModel.h b/Userland/DevTools/HackStudio/Git/GitFilesModel.h
new file mode 100644
index 0000000000..a3e1483ded
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Git/GitFilesModel.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "GitRepo.h"
+#include <AK/LexicalPath.h>
+#include <AK/NonnullRefPtr.h>
+#include <LibGUI/Model.h>
+
+namespace HackStudio {
+
+class GitFilesModel final : public GUI::Model {
+public:
+ static NonnullRefPtr<GitFilesModel> create(Vector<LexicalPath>&& files);
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_files.size(); }
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return 1; }
+
+ virtual String column_name(int) const override
+ {
+ return "";
+ }
+
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+
+ virtual void update() override { }
+ virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex&) const override;
+
+private:
+ explicit GitFilesModel(Vector<LexicalPath>&& files);
+ Vector<LexicalPath> m_files;
+};
+}
diff --git a/Userland/DevTools/HackStudio/Git/GitFilesView.cpp b/Userland/DevTools/HackStudio/Git/GitFilesView.cpp
new file mode 100644
index 0000000000..76aa3abda8
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Git/GitFilesView.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "GitFilesView.h"
+#include <LibGUI/Model.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/ScrollBar.h>
+#include <LibGfx/Palette.h>
+
+namespace HackStudio {
+GitFilesView::~GitFilesView()
+{
+}
+
+void GitFilesView::paint_list_item(GUI::Painter& painter, int row_index, int painted_item_index)
+{
+ ListView::paint_list_item(painter, row_index, painted_item_index);
+
+ painter.blit(action_icon_rect((size_t)painted_item_index).top_left(), *m_action_icon, m_action_icon->rect());
+}
+
+Gfx::IntRect GitFilesView::action_icon_rect(size_t painted_item_index)
+{
+ int y = painted_item_index * item_height();
+ return { content_width() - 20, y, m_action_icon->rect().width(), m_action_icon->rect().height() };
+}
+
+GitFilesView::GitFilesView(GitFileActionCallback callback, NonnullRefPtr<Gfx::Bitmap> action_icon)
+ : m_action_callback(move(callback))
+ , m_action_icon(action_icon)
+{
+ set_alternating_row_colors(false);
+}
+
+void GitFilesView::mousedown_event(GUI::MouseEvent& event)
+{
+ if (event.button() != GUI::MouseButton::Left) {
+ ListView::mousedown_event(event);
+ return;
+ }
+
+ if (event.x() < action_icon_rect(0).x() || event.x() > action_icon_rect(0).top_right().x()) {
+ ListView::mousedown_event(event);
+ return;
+ }
+
+ size_t item_index = (event.y() + vertical_scrollbar().value()) / item_height();
+ if (model()->row_count() == 0 || item_index > (size_t)model()->row_count()) {
+ ListView::mousedown_event(event);
+ return;
+ }
+
+ auto data = model()->index(item_index, model_column()).data();
+
+ ASSERT(data.is_string());
+ m_action_callback(LexicalPath(data.to_string()));
+}
+
+};
diff --git a/Userland/DevTools/HackStudio/Git/GitFilesView.h b/Userland/DevTools/HackStudio/Git/GitFilesView.h
new file mode 100644
index 0000000000..8eb636304c
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Git/GitFilesView.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/LexicalPath.h>
+#include <LibGUI/ListView.h>
+#include <LibGfx/Bitmap.h>
+
+namespace HackStudio {
+
+// A "GitFileAction" is either the staging or the unstaging of a file.
+typedef Function<void(const LexicalPath& file)> GitFileActionCallback;
+
+class GitFilesView : public GUI::ListView {
+ C_OBJECT(GitFilesView)
+public:
+ virtual ~GitFilesView() override;
+
+protected:
+ GitFilesView(GitFileActionCallback, NonnullRefPtr<Gfx::Bitmap> action_icon);
+
+private:
+ virtual void paint_list_item(GUI::Painter& painter, int row_index, int painted_item_index);
+
+ virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual Gfx::IntRect action_icon_rect(size_t painted_item_index);
+
+ GitFileActionCallback m_action_callback;
+ NonnullRefPtr<Gfx::Bitmap> m_action_icon;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Git/GitRepo.cpp b/Userland/DevTools/HackStudio/Git/GitRepo.cpp
new file mode 100644
index 0000000000..ea68c88ff6
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Git/GitRepo.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "GitRepo.h"
+#include <LibCore/Command.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace HackStudio {
+
+GitRepo::CreateResult GitRepo::try_to_create(const LexicalPath& repository_root)
+{
+ if (!git_is_installed()) {
+ return { CreateResult::Type::GitProgramNotFound, nullptr };
+ }
+ if (!git_repo_exists(repository_root)) {
+ return { CreateResult::Type::NoGitRepo, nullptr };
+ }
+
+ return { CreateResult::Type::Success, adopt(*new GitRepo(repository_root)) };
+}
+
+RefPtr<GitRepo> GitRepo::initialize_repository(const LexicalPath& repository_root)
+{
+ auto res = command_wrapper({ "init" }, repository_root);
+ if (res.is_null())
+ return {};
+
+ ASSERT(git_repo_exists(repository_root));
+ return adopt(*new GitRepo(repository_root));
+}
+
+Vector<LexicalPath> GitRepo::unstaged_files() const
+{
+ auto modified = modified_files();
+ auto untracked = untracked_files();
+ modified.append(move(untracked));
+ return modified;
+}
+//
+Vector<LexicalPath> GitRepo::staged_files() const
+{
+ auto raw_result = command({ "diff", "--cached", "--name-only" });
+ if (raw_result.is_null())
+ return {};
+ return parse_files_list(raw_result);
+}
+
+Vector<LexicalPath> GitRepo::modified_files() const
+{
+ auto raw_result = command({ "ls-files", "--modified", "--exclude-standard" });
+ if (raw_result.is_null())
+ return {};
+ return parse_files_list(raw_result);
+}
+
+Vector<LexicalPath> GitRepo::untracked_files() const
+{
+ auto raw_result = command({ "ls-files", "--others", "--exclude-standard" });
+ if (raw_result.is_null())
+ return {};
+ return parse_files_list(raw_result);
+}
+
+Vector<LexicalPath> GitRepo::parse_files_list(const String& raw_result)
+{
+ auto lines = raw_result.split('\n');
+ Vector<LexicalPath> files;
+ for (const auto& line : lines) {
+ files.empend(line);
+ }
+ return files;
+}
+
+String GitRepo::command(const Vector<String>& command_parts) const
+{
+ return command_wrapper(command_parts, m_repository_root);
+}
+
+String GitRepo::command_wrapper(const Vector<String>& command_parts, const LexicalPath& chdir)
+{
+ return Core::command("git", command_parts, chdir);
+}
+
+bool GitRepo::git_is_installed()
+{
+ return !command_wrapper({ "--help" }, LexicalPath("/")).is_null();
+}
+
+bool GitRepo::git_repo_exists(const LexicalPath& repo_root)
+{
+ return !command_wrapper({ "status" }, repo_root).is_null();
+}
+
+bool GitRepo::stage(const LexicalPath& file)
+{
+ return !command({ "add", file.string() }).is_null();
+}
+
+bool GitRepo::unstage(const LexicalPath& file)
+{
+ return !command({ "reset", "HEAD", "--", file.string() }).is_null();
+}
+
+bool GitRepo::commit(const String& message)
+{
+ return !command({ "commit", "-m", message }).is_null();
+}
+
+Optional<String> GitRepo::original_file_content(const LexicalPath& file) const
+{
+ return command({ "show", String::formatted("HEAD:{}", file) });
+}
+
+Optional<String> GitRepo::unstaged_diff(const LexicalPath& file) const
+{
+ return command({ "diff", file.string().characters() });
+}
+
+bool GitRepo::is_tracked(const LexicalPath& file) const
+{
+ auto res = command({ "ls-files", file.string() });
+ if (res.is_null())
+ return false;
+ return !res.is_empty();
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/Git/GitRepo.h b/Userland/DevTools/HackStudio/Git/GitRepo.h
new file mode 100644
index 0000000000..03037b224a
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Git/GitRepo.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/LexicalPath.h>
+#include <AK/Optional.h>
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+
+namespace HackStudio {
+
+class GitRepo final : public RefCounted<GitRepo> {
+public:
+ struct CreateResult {
+ enum class Type {
+ Success,
+ NoGitRepo,
+ GitProgramNotFound,
+ };
+ Type type;
+ RefPtr<GitRepo> repo;
+ };
+
+ static CreateResult try_to_create(const LexicalPath& repository_root);
+ static RefPtr<GitRepo> initialize_repository(const LexicalPath& repository_root);
+
+ Vector<LexicalPath> unstaged_files() const;
+ Vector<LexicalPath> staged_files() const;
+ bool stage(const LexicalPath& file);
+ bool unstage(const LexicalPath& file);
+ bool commit(const String& message);
+ Optional<String> original_file_content(const LexicalPath& file) const;
+ Optional<String> unstaged_diff(const LexicalPath& file) const;
+ bool is_tracked(const LexicalPath& file) const;
+
+private:
+ static String command_wrapper(const Vector<String>& command_parts, const LexicalPath& chdir);
+ static bool git_is_installed();
+ static bool git_repo_exists(const LexicalPath& repo_root);
+ static Vector<LexicalPath> parse_files_list(const String&);
+
+ explicit GitRepo(const LexicalPath& repository_root)
+ : m_repository_root(repository_root)
+ {
+ }
+
+ Vector<LexicalPath> modified_files() const;
+ Vector<LexicalPath> untracked_files() const;
+
+ String command(const Vector<String>& command_parts) const;
+
+ LexicalPath m_repository_root;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Git/GitWidget.cpp b/Userland/DevTools/HackStudio/Git/GitWidget.cpp
new file mode 100644
index 0000000000..95ef6928ec
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Git/GitWidget.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "GitWidget.h"
+#include "GitFilesModel.h"
+#include <AK/LogStream.h>
+#include <LibCore/File.h>
+#include <LibDiff/Format.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/InputBox.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/Painter.h>
+#include <LibGfx/Bitmap.h>
+#include <stdio.h>
+
+namespace HackStudio {
+
+GitWidget::GitWidget(const LexicalPath& repo_root)
+ : m_repo_root(repo_root)
+{
+ set_layout<GUI::HorizontalBoxLayout>();
+
+ auto& unstaged = add<GUI::Widget>();
+ unstaged.set_layout<GUI::VerticalBoxLayout>();
+ auto& unstaged_header = unstaged.add<GUI::Widget>();
+ unstaged_header.set_layout<GUI::HorizontalBoxLayout>();
+
+ auto& refresh_button = unstaged_header.add<GUI::Button>();
+ refresh_button.set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/reload.png"));
+ refresh_button.set_fixed_size(16, 16);
+ refresh_button.set_tooltip("refresh");
+ refresh_button.on_click = [this](int) { refresh(); };
+
+ auto& unstaged_label = unstaged_header.add<GUI::Label>();
+ unstaged_label.set_text("Unstaged");
+
+ unstaged_header.set_fixed_height(20);
+ m_unstaged_files = unstaged.add<GitFilesView>(
+ [this](const auto& file) { stage_file(file); },
+ Gfx::Bitmap::load_from_file("/res/icons/16x16/plus.png").release_nonnull());
+ m_unstaged_files->on_selection = [this](const GUI::ModelIndex& index) {
+ const auto& selected = index.data().as_string();
+ show_diff(LexicalPath(selected));
+ };
+
+ auto& staged = add<GUI::Widget>();
+ staged.set_layout<GUI::VerticalBoxLayout>();
+
+ auto& staged_header = staged.add<GUI::Widget>();
+ staged_header.set_layout<GUI::HorizontalBoxLayout>();
+
+ auto& commit_button = staged_header.add<GUI::Button>();
+ commit_button.set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/commit.png"));
+ commit_button.set_fixed_size(16, 16);
+ commit_button.set_tooltip("commit");
+ commit_button.on_click = [this](int) { commit(); };
+
+ auto& staged_label = staged_header.add<GUI::Label>();
+ staged_label.set_text("Staged");
+
+ staged_header.set_fixed_height(20);
+ m_staged_files = staged.add<GitFilesView>(
+ [this](const auto& file) { unstage_file(file); },
+ Gfx::Bitmap::load_from_file("/res/icons/16x16/minus.png").release_nonnull());
+}
+
+bool GitWidget::initialize()
+{
+ auto result = GitRepo::try_to_create(m_repo_root);
+ switch (result.type) {
+ case GitRepo::CreateResult::Type::Success:
+ m_git_repo = result.repo;
+ return true;
+ case GitRepo::CreateResult::Type::GitProgramNotFound:
+ GUI::MessageBox::show(window(), "Please install the Git port", "Error", GUI::MessageBox::Type::Error);
+ return false;
+ case GitRepo::CreateResult::Type::NoGitRepo: {
+ auto decision = GUI::MessageBox::show(window(), "Create git repository?", "Git", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
+ if (decision != GUI::Dialog::ExecResult::ExecYes)
+ return false;
+ m_git_repo = GitRepo::initialize_repository(m_repo_root);
+ return true;
+ }
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+bool GitWidget::initialize_if_needed()
+{
+ if (initialized())
+ return true;
+
+ return initialize();
+}
+
+void GitWidget::refresh()
+{
+ if (!initialize_if_needed()) {
+ dbgln("GitWidget initialization failed");
+ return;
+ }
+
+ ASSERT(!m_git_repo.is_null());
+
+ m_unstaged_files->set_model(GitFilesModel::create(m_git_repo->unstaged_files()));
+ m_staged_files->set_model(GitFilesModel::create(m_git_repo->staged_files()));
+}
+
+void GitWidget::stage_file(const LexicalPath& file)
+{
+ dbgln("staging: {}", file);
+ bool rc = m_git_repo->stage(file);
+ ASSERT(rc);
+ refresh();
+}
+
+void GitWidget::unstage_file(const LexicalPath& file)
+{
+ dbgln("unstaging: {}", file);
+ bool rc = m_git_repo->unstage(file);
+ ASSERT(rc);
+ refresh();
+}
+
+void GitWidget::commit()
+{
+ String message;
+ auto res = GUI::InputBox::show(message, window(), "Commit message:", "Commit");
+ if (res != GUI::InputBox::ExecOK || message.is_empty())
+ return;
+ dbgln("commit message: {}", message);
+ m_git_repo->commit(message);
+ refresh();
+}
+
+void GitWidget::set_view_diff_callback(ViewDiffCallback callback)
+{
+ m_view_diff_callback = move(callback);
+}
+
+void GitWidget::show_diff(const LexicalPath& file_path)
+{
+ if (!m_git_repo->is_tracked(file_path)) {
+ auto file = Core::File::construct(file_path.string());
+ if (!file->open(Core::IODevice::ReadOnly)) {
+ perror("open");
+ ASSERT_NOT_REACHED();
+ }
+
+ auto content = file->read_all();
+ String content_string((char*)content.data(), content.size());
+ m_view_diff_callback("", Diff::generate_only_additions(content_string));
+ return;
+ }
+ const auto& original_content = m_git_repo->original_file_content(file_path);
+ const auto& diff = m_git_repo->unstaged_diff(file_path);
+ ASSERT(original_content.has_value() && diff.has_value());
+ m_view_diff_callback(original_content.value(), diff.value());
+}
+}
diff --git a/Userland/DevTools/HackStudio/Git/GitWidget.h b/Userland/DevTools/HackStudio/Git/GitWidget.h
new file mode 100644
index 0000000000..a17e9f9fd4
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Git/GitWidget.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "GitFilesView.h"
+#include "GitRepo.h"
+#include <AK/Function.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/Widget.h>
+
+namespace HackStudio {
+
+typedef Function<void(const String& original_content, const String& diff)> ViewDiffCallback;
+
+class GitWidget final : public GUI::Widget {
+ C_OBJECT(GitWidget)
+public:
+ virtual ~GitWidget() override { }
+
+ void refresh();
+ void set_view_diff_callback(ViewDiffCallback callback);
+ bool initialized() const { return !m_git_repo.is_null(); };
+
+private:
+ explicit GitWidget(const LexicalPath& repo_root);
+
+ bool initialize();
+ bool initialize_if_needed();
+ void stage_file(const LexicalPath&);
+ void unstage_file(const LexicalPath&);
+ void commit();
+ void show_diff(const LexicalPath&);
+
+ LexicalPath m_repo_root;
+ RefPtr<GitFilesView> m_unstaged_files;
+ RefPtr<GitFilesView> m_staged_files;
+ RefPtr<GitRepo> m_git_repo;
+ ViewDiffCallback m_view_diff_callback;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/HackStudio.h b/Userland/DevTools/HackStudio/HackStudio.h
new file mode 100644
index 0000000000..85247453d8
--- /dev/null
+++ b/Userland/DevTools/HackStudio/HackStudio.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "EditorWrapper.h"
+#include "LanguageClients/ServerConnections.h"
+#include "Project.h"
+#include <AK/String.h>
+#include <LibGUI/TextEditor.h>
+
+namespace HackStudio {
+
+GUI::TextEditor& current_editor();
+void open_file(const String&);
+RefPtr<EditorWrapper> current_editor_wrapper();
+void open_file(const String&);
+Project& project();
+String currently_open_file();
+void set_current_editor_wrapper(RefPtr<EditorWrapper>);
+
+}
diff --git a/Userland/DevTools/HackStudio/HackStudioWidget.cpp b/Userland/DevTools/HackStudio/HackStudioWidget.cpp
new file mode 100644
index 0000000000..7ef0eb33d2
--- /dev/null
+++ b/Userland/DevTools/HackStudio/HackStudioWidget.cpp
@@ -0,0 +1,945 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "HackStudioWidget.h"
+#include "CursorTool.h"
+#include "Debugger/DebugInfoWidget.h"
+#include "Debugger/Debugger.h"
+#include "Debugger/DisassemblyWidget.h"
+#include "Editor.h"
+#include "EditorWrapper.h"
+#include "FindInFilesWidget.h"
+#include "FormEditorWidget.h"
+#include "FormWidget.h"
+#include "Git/DiffViewer.h"
+#include "Git/GitWidget.h"
+#include "HackStudio.h"
+#include "HackStudioWidget.h"
+#include "Locator.h"
+#include "Project.h"
+#include "TerminalWrapper.h"
+#include "WidgetTool.h"
+#include "WidgetTreeModel.h"
+#include <AK/StringBuilder.h>
+#include <LibCore/ArgsParser.h>
+#include <LibCore/Event.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/File.h>
+#include <LibDebug/DebugSession.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/ActionGroup.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/EditingEngine.h>
+#include <LibGUI/FilePicker.h>
+#include <LibGUI/InputBox.h>
+#include <LibGUI/ItemListModel.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/MenuBar.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGUI/RegularEditingEngine.h>
+#include <LibGUI/Splitter.h>
+#include <LibGUI/StackWidget.h>
+#include <LibGUI/TabWidget.h>
+#include <LibGUI/TableView.h>
+#include <LibGUI/TextBox.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGUI/ToolBar.h>
+#include <LibGUI/ToolBarContainer.h>
+#include <LibGUI/TreeView.h>
+#include <LibGUI/VimEditingEngine.h>
+#include <LibGUI/Widget.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibThread/Lock.h>
+#include <LibThread/Thread.h>
+#include <LibVT/TerminalWidget.h>
+#include <fcntl.h>
+#include <spawn.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+namespace HackStudio {
+
+HackStudioWidget::HackStudioWidget(const String& path_to_project)
+{
+ set_fill_with_background_color(true);
+ set_layout<GUI::VerticalBoxLayout>();
+ layout()->set_spacing(2);
+
+ open_project(path_to_project);
+
+ auto& toolbar_container = add<GUI::ToolBarContainer>();
+
+ auto& outer_splitter = add<GUI::HorizontalSplitter>();
+
+ auto& left_hand_splitter = outer_splitter.add<GUI::VerticalSplitter>();
+ left_hand_splitter.set_fixed_width(150);
+ create_project_tree_view(left_hand_splitter);
+ m_project_tree_view_context_menu = create_project_tree_view_context_menu();
+
+ create_open_files_view(left_hand_splitter);
+
+ m_right_hand_splitter = outer_splitter.add<GUI::VerticalSplitter>();
+ m_right_hand_stack = m_right_hand_splitter->add<GUI::StackWidget>();
+
+ // Put a placeholder widget front & center since we don't have a file open yet.
+ m_right_hand_stack->add<GUI::Widget>();
+
+ create_form_editor(*m_right_hand_stack);
+
+ m_diff_viewer = m_right_hand_stack->add<DiffViewer>();
+
+ m_editors_splitter = m_right_hand_stack->add<GUI::VerticalSplitter>();
+ m_editors_splitter->layout()->set_margins({ 0, 3, 0, 0 });
+ add_new_editor(*m_editors_splitter);
+
+ m_switch_to_next_editor = create_switch_to_next_editor_action();
+ m_switch_to_previous_editor = create_switch_to_previous_editor_action();
+
+ m_remove_current_editor_action = create_remove_current_editor_action();
+ m_open_action = create_open_action();
+ m_save_action = create_save_action();
+
+ create_action_tab(*m_right_hand_splitter);
+
+ m_add_editor_action = create_add_editor_action();
+ m_add_terminal_action = create_add_terminal_action();
+ m_remove_current_terminal_action = create_remove_current_terminal_action();
+
+ m_locator = add<Locator>();
+
+ m_terminal_wrapper->on_command_exit = [this] {
+ m_stop_action->set_enabled(false);
+ };
+
+ m_build_action = create_build_action();
+ m_run_action = create_run_action();
+ m_stop_action = create_stop_action();
+ m_debug_action = create_debug_action();
+
+ initialize_debugger();
+
+ create_toolbar(toolbar_container);
+}
+
+void HackStudioWidget::update_actions()
+{
+ auto is_remove_terminal_enabled = [this]() {
+ auto widget = m_action_tab_widget->active_widget();
+ if (!widget)
+ return false;
+ if (StringView { "TerminalWrapper" } != widget->class_name())
+ return false;
+ if (!reinterpret_cast<TerminalWrapper*>(widget)->user_spawned())
+ return false;
+ return true;
+ };
+
+ m_remove_current_editor_action->set_enabled(m_all_editor_wrappers.size() > 1);
+ m_remove_current_terminal_action->set_enabled(is_remove_terminal_enabled());
+}
+
+void HackStudioWidget::on_action_tab_change()
+{
+ update_actions();
+ auto git_widget = m_action_tab_widget->active_widget();
+ if (!git_widget)
+ return;
+ if (StringView { "GitWidget" } != git_widget->class_name())
+ return;
+ reinterpret_cast<GitWidget*>(git_widget)->refresh();
+}
+
+void HackStudioWidget::open_project(const String& root_path)
+{
+ if (chdir(root_path.characters()) < 0) {
+ perror("chdir");
+ exit(1);
+ }
+ m_project = Project::open_with_root_path(root_path);
+ ASSERT(m_project);
+ if (m_project_tree_view) {
+ m_project_tree_view->set_model(m_project->model());
+ m_project_tree_view->toggle_index(m_project_tree_view->model()->index(0, 0));
+ m_project_tree_view->update();
+ }
+ if (Debugger::is_initialized()) {
+ Debugger::the().reset_breakpoints();
+ }
+}
+
+Vector<String> HackStudioWidget::selected_file_names() const
+{
+ Vector<String> files;
+ m_project_tree_view->selection().for_each_index([&](const GUI::ModelIndex& index) {
+ files.append(index.data().as_string());
+ });
+ return files;
+}
+
+void HackStudioWidget::open_file(const String& filename)
+{
+ if (!currently_open_file().is_empty()) {
+ // Since the file is previously open, it should always be in m_open_files.
+ ASSERT(m_open_files.find(currently_open_file()) != m_open_files.end());
+ auto previous_open_project_file = m_open_files.get(currently_open_file()).value();
+
+ // Update the scrollbar values of the previous_open_project_file and save them to m_open_files.
+ previous_open_project_file->vertical_scroll_value(current_editor().vertical_scrollbar().value());
+ previous_open_project_file->horizontal_scroll_value(current_editor().horizontal_scrollbar().value());
+ m_open_files.set(currently_open_file(), previous_open_project_file);
+ }
+
+ RefPtr<ProjectFile> new_project_file = nullptr;
+ if (auto it = m_open_files.find(filename); it != m_open_files.end()) {
+ new_project_file = it->value;
+ } else {
+ new_project_file = m_project->get_file(filename);
+ if (!new_project_file) {
+ new_project_file = ProjectFile::construct_with_name(filename);
+ }
+ m_open_files.set(filename, *new_project_file);
+ m_open_files_vector.append(filename);
+ m_open_files_view->model()->update();
+ }
+
+ current_editor().set_document(const_cast<GUI::TextDocument&>(new_project_file->document()));
+ current_editor().set_mode(GUI::TextEditor::Editable);
+ current_editor().horizontal_scrollbar().set_value(new_project_file->horizontal_scroll_value());
+ current_editor().vertical_scrollbar().set_value(new_project_file->vertical_scroll_value());
+ current_editor().set_editing_engine(make<GUI::RegularEditingEngine>());
+
+ if (filename.ends_with(".frm")) {
+ set_edit_mode(EditMode::Form);
+ } else {
+ set_edit_mode(EditMode::Text);
+ }
+
+ m_currently_open_file = filename;
+
+ String relative_file_path = m_currently_open_file;
+ if (m_currently_open_file.starts_with(m_project->root_path()))
+ relative_file_path = m_currently_open_file.substring(m_project->root_path().length() + 1);
+
+ window()->set_title(String::formatted("{} - {} - Hack Studio", relative_file_path, m_project->name()));
+ m_project_tree_view->update();
+
+ current_editor_wrapper().filename_label().set_text(filename);
+
+ current_editor().set_focus(true);
+}
+
+EditorWrapper& HackStudioWidget::current_editor_wrapper()
+{
+ ASSERT(m_current_editor_wrapper);
+ return *m_current_editor_wrapper;
+}
+
+GUI::TextEditor& HackStudioWidget::current_editor()
+{
+ return current_editor_wrapper().editor();
+}
+
+void HackStudioWidget::set_edit_mode(EditMode mode)
+{
+ if (mode == EditMode::Text) {
+ m_right_hand_stack->set_active_widget(m_editors_splitter);
+ } else if (mode == EditMode::Form) {
+ m_right_hand_stack->set_active_widget(m_form_inner_container);
+ } else if (mode == EditMode::Diff) {
+ m_right_hand_stack->set_active_widget(m_diff_viewer);
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+ m_right_hand_stack->active_widget()->update();
+}
+
+NonnullRefPtr<GUI::Menu> HackStudioWidget::create_project_tree_view_context_menu()
+{
+ m_open_selected_action = create_open_selected_action();
+ m_new_action = create_new_action();
+ m_delete_action = create_delete_action();
+ auto project_tree_view_context_menu = GUI::Menu::construct("Project Files");
+ project_tree_view_context_menu->add_action(*m_open_selected_action);
+ // TODO: Rename, cut, copy, duplicate with new name, show containing folder ...
+ project_tree_view_context_menu->add_separator();
+ project_tree_view_context_menu->add_action(*m_new_action);
+ project_tree_view_context_menu->add_action(*m_delete_action);
+ return project_tree_view_context_menu;
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_new_action()
+{
+ return GUI::Action::create("Add new file to project...", { Mod_Ctrl, Key_N }, Gfx::Bitmap::load_from_file("/res/icons/16x16/new.png"), [this](const GUI::Action&) {
+ String filename;
+ if (GUI::InputBox::show(filename, window(), "Enter name of new file:", "Add new file to project") != GUI::InputBox::ExecOK)
+ return;
+ auto file = Core::File::construct(filename);
+ if (!file->open((Core::IODevice::OpenMode)(Core::IODevice::WriteOnly | Core::IODevice::MustBeNew))) {
+ GUI::MessageBox::show(window(), String::formatted("Failed to create '{}'", filename), "Error", GUI::MessageBox::Type::Error);
+ return;
+ }
+ m_project_tree_view->toggle_index(m_project_tree_view->model()->index(0, 0));
+ open_file(filename);
+ });
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_open_selected_action()
+{
+
+ auto open_selected_action = GUI::Action::create("Open", [this](const GUI::Action&) {
+ auto files = selected_file_names();
+ for (auto& file : files)
+ open_file(file);
+ });
+ open_selected_action->set_enabled(true);
+ return open_selected_action;
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_delete_action()
+{
+ auto delete_action = GUI::CommonActions::make_delete_action([this](const GUI::Action&) {
+ auto files = selected_file_names();
+ if (files.is_empty())
+ return;
+
+ String message;
+ if (files.size() == 1) {
+ message = String::formatted("Really remove {} from disk?", LexicalPath(files[0]).basename());
+ } else {
+ message = String::formatted("Really remove {} files from disk?", files.size());
+ }
+
+ auto result = GUI::MessageBox::show(window(),
+ message,
+ "Confirm deletion",
+ GUI::MessageBox::Type::Warning,
+ GUI::MessageBox::InputType::OKCancel);
+ if (result == GUI::MessageBox::ExecCancel)
+ return;
+
+ for (auto& file : files) {
+ if (1) {
+ // FIXME: Remove `file` from disk
+ } else {
+ GUI::MessageBox::show(window(),
+ String::formatted("Removing file {} from the project failed.", file),
+ "Removal failed",
+ GUI::MessageBox::Type::Error);
+ break;
+ }
+ }
+ });
+ delete_action->set_enabled(false);
+ return delete_action;
+}
+
+void HackStudioWidget::add_new_editor(GUI::Widget& parent)
+{
+ auto wrapper = EditorWrapper::construct();
+ if (m_action_tab_widget) {
+ parent.insert_child_before(wrapper, *m_action_tab_widget);
+ } else {
+ parent.add_child(wrapper);
+ }
+ m_current_editor_wrapper = wrapper;
+ m_all_editor_wrappers.append(wrapper);
+ wrapper->editor().set_focus(true);
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_switch_to_next_editor_action()
+{
+ return GUI::Action::create("Switch to next editor", { Mod_Ctrl, Key_E }, [this](auto&) {
+ if (m_all_editor_wrappers.size() <= 1)
+ return;
+ Vector<EditorWrapper*> wrappers;
+ m_editors_splitter->for_each_child_of_type<EditorWrapper>([this, &wrappers](auto& child) {
+ wrappers.append(&child);
+ return IterationDecision::Continue;
+ });
+ for (size_t i = 0; i < wrappers.size(); ++i) {
+ if (m_current_editor_wrapper.ptr() == wrappers[i]) {
+ if (i == wrappers.size() - 1)
+ wrappers[0]->editor().set_focus(true);
+ else
+ wrappers[i + 1]->editor().set_focus(true);
+ }
+ }
+ });
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_switch_to_previous_editor_action()
+{
+ return GUI::Action::create("Switch to previous editor", { Mod_Ctrl | Mod_Shift, Key_E }, [this](auto&) {
+ if (m_all_editor_wrappers.size() <= 1)
+ return;
+ Vector<EditorWrapper*> wrappers;
+ m_editors_splitter->for_each_child_of_type<EditorWrapper>([this, &wrappers](auto& child) {
+ wrappers.append(&child);
+ return IterationDecision::Continue;
+ });
+ for (int i = wrappers.size() - 1; i >= 0; --i) {
+ if (m_current_editor_wrapper.ptr() == wrappers[i]) {
+ if (i == 0)
+ wrappers.last()->editor().set_focus(true);
+ else
+ wrappers[i - 1]->editor().set_focus(true);
+ }
+ }
+ });
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_remove_current_editor_action()
+{
+ return GUI::Action::create("Remove current editor", { Mod_Alt | Mod_Shift, Key_E }, [this](auto&) {
+ if (m_all_editor_wrappers.size() <= 1)
+ return;
+ auto wrapper = m_current_editor_wrapper;
+ m_switch_to_next_editor->activate();
+ m_editors_splitter->remove_child(*wrapper);
+ m_all_editor_wrappers.remove_first_matching([&wrapper](auto& entry) { return entry == wrapper.ptr(); });
+ update_actions();
+ });
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_open_action()
+{
+ return GUI::Action::create("Open project...", { Mod_Ctrl | Mod_Shift, Key_O }, Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"), [this](auto&) {
+ auto open_path = GUI::FilePicker::get_open_filepath(window(), "Open project");
+ if (!open_path.has_value())
+ return;
+ open_project(open_path.value());
+ update_actions();
+ });
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_save_action()
+{
+ return GUI::Action::create("Save", { Mod_Ctrl, Key_S }, Gfx::Bitmap::load_from_file("/res/icons/16x16/save.png"), [this](auto&) {
+ if (m_currently_open_file.is_empty())
+ return;
+
+ current_editor().write_to_file(m_currently_open_file);
+
+ if (m_git_widget->initialized())
+ m_git_widget->refresh();
+ });
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_remove_current_terminal_action()
+{
+ return GUI::Action::create("Remove current Terminal", { Mod_Alt | Mod_Shift, Key_T }, [this](auto&) {
+ auto widget = m_action_tab_widget->active_widget();
+ if (!widget)
+ return;
+ if (!is<TerminalWrapper>(widget))
+ return;
+ auto& terminal = *static_cast<TerminalWrapper*>(widget);
+ if (!terminal.user_spawned())
+ return;
+ m_action_tab_widget->remove_tab(terminal);
+ update_actions();
+ });
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_add_editor_action()
+{
+ return GUI::Action::create("Add new editor", { Mod_Ctrl | Mod_Alt, Key_E },
+ Gfx::Bitmap::load_from_file("/res/icons/16x16/app-text-editor.png"),
+ [this](auto&) {
+ add_new_editor(*m_editors_splitter);
+ update_actions();
+ });
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_add_terminal_action()
+{
+ return GUI::Action::create("Add new Terminal", { Mod_Ctrl | Mod_Alt, Key_T },
+ Gfx::Bitmap::load_from_file("/res/icons/16x16/app-terminal.png"),
+ [this](auto&) {
+ auto& terminal_wrapper = m_action_tab_widget->add_tab<TerminalWrapper>("Terminal");
+ reveal_action_tab(terminal_wrapper);
+ update_actions();
+ terminal_wrapper.terminal().set_focus(true);
+ });
+}
+
+void HackStudioWidget::reveal_action_tab(GUI::Widget& widget)
+{
+ if (m_action_tab_widget->min_height() < 200)
+ m_action_tab_widget->set_fixed_height(200);
+ m_action_tab_widget->set_active_widget(&widget);
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_debug_action()
+{
+ return GUI::Action::create("Debug", Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-run.png"), [this](auto&) {
+ if (!GUI::FilePicker::file_exists(get_project_executable_path())) {
+ GUI::MessageBox::show(window(), String::formatted("Could not find file: {}. (did you build the project?)", get_project_executable_path()), "Error", GUI::MessageBox::Type::Error);
+ return;
+ }
+ if (Debugger::the().session()) {
+ GUI::MessageBox::show(window(), "Debugger is already running", "Error", GUI::MessageBox::Type::Error);
+ return;
+ }
+
+ Debugger::the().set_executable_path(get_project_executable_path());
+ m_debugger_thread = LibThread::Thread::construct(Debugger::start_static);
+ m_debugger_thread->start();
+ });
+}
+
+void HackStudioWidget::initialize_debugger()
+{
+ Debugger::initialize(
+ m_project->root_path(),
+ [this](const PtraceRegisters& regs) {
+ ASSERT(Debugger::the().session());
+ const auto& debug_session = *Debugger::the().session();
+ auto source_position = debug_session.get_source_position(regs.eip);
+ if (!source_position.has_value()) {
+ dbgln("Could not find source position for address: {:p}", regs.eip);
+ return Debugger::HasControlPassedToUser::No;
+ }
+ dbgln("Debugger stopped at source position: {}:{}", source_position.value().file_path, source_position.value().line_number);
+
+ Core::EventLoop::main().post_event(
+ *window(),
+ make<Core::DeferredInvocationEvent>(
+ [this, source_position, &regs](auto&) {
+ m_current_editor_in_execution = get_editor_of_file(source_position.value().file_path);
+ m_current_editor_in_execution->editor().set_execution_position(source_position.value().line_number - 1);
+ m_debug_info_widget->update_state(*Debugger::the().session(), regs);
+ m_debug_info_widget->set_debug_actions_enabled(true);
+ m_disassembly_widget->update_state(*Debugger::the().session(), regs);
+ HackStudioWidget::reveal_action_tab(*m_debug_info_widget);
+ }));
+ Core::EventLoop::wake();
+
+ return Debugger::HasControlPassedToUser::Yes;
+ },
+ [this]() {
+ Core::EventLoop::main().post_event(*window(), make<Core::DeferredInvocationEvent>([this](auto&) {
+ m_debug_info_widget->set_debug_actions_enabled(false);
+ if (m_current_editor_in_execution) {
+ m_current_editor_in_execution->editor().clear_execution_position();
+ }
+ }));
+ Core::EventLoop::wake();
+ },
+ [this]() {
+ Core::EventLoop::main().post_event(*window(), make<Core::DeferredInvocationEvent>([this](auto&) {
+ m_debug_info_widget->program_stopped();
+ m_disassembly_widget->program_stopped();
+ HackStudioWidget::hide_action_tabs();
+ GUI::MessageBox::show(window(), "Program Exited", "Debugger", GUI::MessageBox::Type::Information);
+ }));
+ Core::EventLoop::wake();
+ });
+}
+
+String HackStudioWidget::get_full_path_of_serenity_source(const String& file)
+{
+ auto path_parts = LexicalPath(file).parts();
+ ASSERT(path_parts[0] == "..");
+ path_parts.remove(0);
+ StringBuilder relative_path_builder;
+ relative_path_builder.join("/", path_parts);
+ constexpr char SERENITY_LIBS_PREFIX[] = "/usr/src/serenity";
+ LexicalPath serenity_sources_base(SERENITY_LIBS_PREFIX);
+ return String::formatted("{}/{}", serenity_sources_base, relative_path_builder.to_string());
+}
+
+NonnullRefPtr<EditorWrapper> HackStudioWidget::get_editor_of_file(const String& file_name)
+{
+
+ String file_path = file_name;
+
+ // TODO: We can probably do a more specific condition here, something like
+ // "if (file.starts_with("../Libraries/") || file.starts_with("../AK/"))"
+ if (file_name.starts_with("../")) {
+ file_path = get_full_path_of_serenity_source(file_name);
+ }
+
+ open_file(file_path);
+ return current_editor_wrapper();
+}
+
+String HackStudioWidget::get_project_executable_path() const
+{
+ // FIXME: Dumb heuristic ahead!
+ // e.g /my/project => /my/project/project
+ // TODO: Perhaps a Makefile rule for getting the value of $(PROGRAM) would be better?
+ return String::formatted("{}/{}", m_project->root_path(), LexicalPath(m_project->root_path()).basename());
+}
+
+void HackStudioWidget::build(TerminalWrapper& wrapper)
+{
+ if (m_currently_open_file.ends_with(".js"))
+ wrapper.run_command(String::formatted("js -A {}", m_currently_open_file));
+ else
+ wrapper.run_command("make");
+}
+
+void HackStudioWidget::run(TerminalWrapper& wrapper)
+{
+ if (m_currently_open_file.ends_with(".js"))
+ wrapper.run_command(String::formatted("js {}", m_currently_open_file));
+ else
+ wrapper.run_command("make run");
+}
+
+void HackStudioWidget::hide_action_tabs()
+{
+ m_action_tab_widget->set_fixed_height(24);
+};
+
+Project& HackStudioWidget::project()
+{
+ return *m_project;
+}
+
+void HackStudioWidget::set_current_editor_wrapper(RefPtr<EditorWrapper> editor_wrapper)
+{
+ m_current_editor_wrapper = editor_wrapper;
+}
+
+void HackStudioWidget::create_project_tree_view(GUI::Widget& parent)
+{
+ m_project_tree_view = parent.add<GUI::TreeView>();
+ m_project_tree_view->set_model(m_project->model());
+
+ for (int column_index = 0; column_index < m_project->model().column_count(); ++column_index)
+ m_project_tree_view->set_column_hidden(column_index, true);
+
+ m_project_tree_view->set_column_hidden(GUI::FileSystemModel::Column::Name, false);
+
+ m_project_tree_view->on_context_menu_request = [this](const GUI::ModelIndex& index, const GUI::ContextMenuEvent& event) {
+ if (index.is_valid()) {
+ m_project_tree_view_context_menu->popup(event.screen_position(), m_open_selected_action);
+ }
+ };
+
+ m_project_tree_view->on_selection_change = [this] {
+ m_open_selected_action->set_enabled(!m_project_tree_view->selection().is_empty());
+ m_delete_action->set_enabled(!m_project_tree_view->selection().is_empty());
+ };
+
+ m_project_tree_view->on_activation = [this](auto& index) {
+ auto filename = index.data(GUI::ModelRole::Custom).to_string();
+ open_file(filename);
+ };
+}
+
+void HackStudioWidget::create_open_files_view(GUI::Widget& parent)
+{
+ m_open_files_view = parent.add<GUI::ListView>();
+ auto open_files_model = GUI::ItemListModel<String>::create(m_open_files_vector);
+ m_open_files_view->set_model(open_files_model);
+
+ m_open_files_view->on_activation = [this](auto& index) {
+ open_file(index.data().to_string());
+ };
+}
+
+void HackStudioWidget::create_form_editor(GUI::Widget& parent)
+{
+ m_form_inner_container = parent.add<GUI::Widget>();
+ m_form_inner_container->set_layout<GUI::HorizontalBoxLayout>();
+ auto& form_widgets_toolbar = m_form_inner_container->add<GUI::ToolBar>(Orientation::Vertical, 26);
+ form_widgets_toolbar.set_fixed_width(38);
+
+ GUI::ActionGroup tool_actions;
+ tool_actions.set_exclusive(true);
+
+ auto cursor_tool_action = GUI::Action::create_checkable("Cursor", Gfx::Bitmap::load_from_file("/res/icons/hackstudio/Cursor.png"), [this](auto&) {
+ m_form_editor_widget->set_tool(make<CursorTool>(*m_form_editor_widget));
+ });
+ cursor_tool_action->set_checked(true);
+ tool_actions.add_action(cursor_tool_action);
+
+ form_widgets_toolbar.add_action(cursor_tool_action);
+
+ GUI::WidgetClassRegistration::for_each([&, this](const GUI::WidgetClassRegistration& reg) {
+ constexpr size_t gui_namespace_prefix_length = sizeof("GUI::") - 1;
+ auto icon_path = String::formatted("/res/icons/hackstudio/G{}.png",
+ reg.class_name().substring(gui_namespace_prefix_length, reg.class_name().length() - gui_namespace_prefix_length));
+ if (!Core::File::exists(icon_path))
+ return;
+
+ auto action = GUI::Action::create_checkable(reg.class_name(), Gfx::Bitmap::load_from_file(icon_path), [&reg, this](auto&) {
+ m_form_editor_widget->set_tool(make<WidgetTool>(*m_form_editor_widget, reg));
+ auto widget = reg.construct();
+ m_form_editor_widget->form_widget().add_child(widget);
+ widget->set_relative_rect(30, 30, 30, 30);
+ m_form_editor_widget->model().update();
+ });
+ action->set_checked(false);
+ tool_actions.add_action(action);
+ form_widgets_toolbar.add_action(move(action));
+ });
+
+ auto& form_editor_inner_splitter = m_form_inner_container->add<GUI::HorizontalSplitter>();
+
+ m_form_editor_widget = form_editor_inner_splitter.add<FormEditorWidget>();
+
+ auto& form_editing_pane_container = form_editor_inner_splitter.add<GUI::VerticalSplitter>();
+ form_editing_pane_container.set_fixed_width(190);
+ form_editing_pane_container.set_layout<GUI::VerticalBoxLayout>();
+
+ auto add_properties_pane = [&](auto& text, auto& pane_widget) {
+ auto& wrapper = form_editing_pane_container.add<GUI::Widget>();
+ wrapper.set_layout<GUI::VerticalBoxLayout>();
+ auto& label = wrapper.add<GUI::Label>(text);
+ label.set_fill_with_background_color(true);
+ label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
+ label.set_font(Gfx::FontDatabase::default_bold_font());
+ label.set_fixed_height(16);
+ wrapper.add_child(pane_widget);
+ };
+
+ m_form_widget_tree_view = GUI::TreeView::construct();
+ m_form_widget_tree_view->set_model(m_form_editor_widget->model());
+ m_form_widget_tree_view->on_selection_change = [this] {
+ m_form_editor_widget->selection().disable_hooks();
+ m_form_editor_widget->selection().clear();
+ m_form_widget_tree_view->selection().for_each_index([this](auto& index) {
+ // NOTE: Make sure we don't add the FormWidget itself to the selection,
+ // since that would allow you to drag-move the FormWidget.
+ if (index.internal_data() != &m_form_editor_widget->form_widget())
+ m_form_editor_widget->selection().add(*(GUI::Widget*)index.internal_data());
+ });
+ m_form_editor_widget->update();
+ m_form_editor_widget->selection().enable_hooks();
+ };
+
+ m_form_editor_widget->selection().on_add = [this](auto& widget) {
+ m_form_widget_tree_view->selection().add(m_form_editor_widget->model().index_for_widget(widget));
+ };
+ m_form_editor_widget->selection().on_remove = [this](auto& widget) {
+ m_form_widget_tree_view->selection().remove(m_form_editor_widget->model().index_for_widget(widget));
+ };
+ m_form_editor_widget->selection().on_clear = [this] {
+ m_form_widget_tree_view->selection().clear();
+ };
+
+ add_properties_pane("Form widget tree:", *m_form_widget_tree_view);
+ add_properties_pane("Widget properties:", *GUI::TableView::construct());
+}
+
+void HackStudioWidget::create_toolbar(GUI::Widget& parent)
+{
+ auto& toolbar = parent.add<GUI::ToolBar>();
+ toolbar.add_action(*m_new_action);
+ toolbar.add_action(*m_save_action);
+ toolbar.add_action(*m_delete_action);
+ toolbar.add_separator();
+
+ toolbar.add_action(GUI::CommonActions::make_cut_action([this](auto&) { current_editor().cut_action().activate(); }));
+ toolbar.add_action(GUI::CommonActions::make_copy_action([this](auto&) { current_editor().copy_action().activate(); }));
+ toolbar.add_action(GUI::CommonActions::make_paste_action([this](auto&) { current_editor().paste_action().activate(); }));
+ toolbar.add_separator();
+ toolbar.add_action(GUI::CommonActions::make_undo_action([this](auto&) { current_editor().undo_action().activate(); }));
+ toolbar.add_action(GUI::CommonActions::make_redo_action([this](auto&) { current_editor().redo_action().activate(); }));
+ toolbar.add_separator();
+
+ toolbar.add_action(*m_build_action);
+ toolbar.add_separator();
+
+ toolbar.add_action(*m_run_action);
+ toolbar.add_action(*m_stop_action);
+ toolbar.add_separator();
+
+ toolbar.add_action(*m_debug_action);
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_build_action()
+{
+ return GUI::Action::create("Build", { Mod_Ctrl, Key_B }, Gfx::Bitmap::load_from_file("/res/icons/16x16/build.png"), [this](auto&) {
+ reveal_action_tab(*m_terminal_wrapper);
+ build(*m_terminal_wrapper);
+ m_stop_action->set_enabled(true);
+ });
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_run_action()
+{
+ return GUI::Action::create("Run", { Mod_Ctrl, Key_R }, Gfx::Bitmap::load_from_file("/res/icons/16x16/program-run.png"), [this](auto&) {
+ reveal_action_tab(*m_terminal_wrapper);
+ run(*m_terminal_wrapper);
+ m_stop_action->set_enabled(true);
+ });
+}
+
+void HackStudioWidget::create_action_tab(GUI::Widget& parent)
+{
+ m_action_tab_widget = parent.add<GUI::TabWidget>();
+
+ m_action_tab_widget->set_fixed_height(24);
+ m_action_tab_widget->on_change = [this](auto&) {
+ on_action_tab_change();
+
+ static bool first_time = true;
+ if (!first_time)
+ m_action_tab_widget->set_fixed_height(200);
+ first_time = false;
+ };
+
+ m_find_in_files_widget = m_action_tab_widget->add_tab<FindInFilesWidget>("Find in files");
+ m_terminal_wrapper = m_action_tab_widget->add_tab<TerminalWrapper>("Build", false);
+ m_debug_info_widget = m_action_tab_widget->add_tab<DebugInfoWidget>("Debug");
+ m_disassembly_widget = m_action_tab_widget->add_tab<DisassemblyWidget>("Disassembly");
+ m_git_widget = m_action_tab_widget->add_tab<GitWidget>("Git", LexicalPath(m_project->root_path()));
+ m_git_widget->set_view_diff_callback([this](const auto& original_content, const auto& diff) {
+ m_diff_viewer->set_content(original_content, diff);
+ set_edit_mode(EditMode::Diff);
+ });
+}
+
+void HackStudioWidget::create_app_menubar(GUI::MenuBar& menubar)
+{
+ auto& app_menu = menubar.add_menu("Hack Studio");
+ app_menu.add_action(*m_open_action);
+ app_menu.add_action(*m_save_action);
+ app_menu.add_separator();
+ app_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
+ GUI::Application::the()->quit();
+ }));
+}
+
+void HackStudioWidget::create_project_menubar(GUI::MenuBar& menubar)
+{
+ auto& project_menu = menubar.add_menu("Project");
+ project_menu.add_action(*m_new_action);
+}
+
+void HackStudioWidget::create_edit_menubar(GUI::MenuBar& menubar)
+{
+ auto& edit_menu = menubar.add_menu("Edit");
+ edit_menu.add_action(GUI::Action::create("Find in files...", { Mod_Ctrl | Mod_Shift, Key_F }, Gfx::Bitmap::load_from_file("/res/icons/16x16/find.png"), [this](auto&) {
+ reveal_action_tab(*m_find_in_files_widget);
+ m_find_in_files_widget->focus_textbox_and_select_all();
+ }));
+
+ edit_menu.add_separator();
+
+ auto line_wrapping_action = GUI::Action::create_checkable("Line wrapping", [this](auto& action) {
+ for (auto& wrapper : m_all_editor_wrappers) {
+ wrapper.editor().set_line_wrapping_enabled(action.is_checked());
+ }
+ });
+ line_wrapping_action->set_checked(current_editor().is_line_wrapping_enabled());
+ edit_menu.add_action(line_wrapping_action);
+
+ edit_menu.add_separator();
+
+ auto vim_emulation_setting_action = GUI::Action::create_checkable("Vim emulation", { Mod_Ctrl | Mod_Shift | Mod_Alt, Key_V }, [this](auto& action) {
+ if (action.is_checked())
+ current_editor().set_editing_engine(make<GUI::VimEditingEngine>());
+ else
+ current_editor().set_editing_engine(make<GUI::RegularEditingEngine>());
+ });
+ vim_emulation_setting_action->set_checked(false);
+ edit_menu.add_action(vim_emulation_setting_action);
+}
+
+void HackStudioWidget::create_build_menubar(GUI::MenuBar& menubar)
+{
+ auto& build_menu = menubar.add_menu("Build");
+ build_menu.add_action(*m_build_action);
+ build_menu.add_separator();
+ build_menu.add_action(*m_run_action);
+ build_menu.add_action(*m_stop_action);
+ build_menu.add_separator();
+ build_menu.add_action(*m_debug_action);
+}
+
+void HackStudioWidget::create_view_menubar(GUI::MenuBar& menubar)
+{
+ auto hide_action_tabs_action = GUI::Action::create("Hide action tabs", { Mod_Ctrl | Mod_Shift, Key_X }, [this](auto&) {
+ hide_action_tabs();
+ });
+ auto open_locator_action = GUI::Action::create("Open locator", { Mod_Ctrl, Key_K }, [this](auto&) {
+ m_locator->open();
+ });
+
+ auto& view_menu = menubar.add_menu("View");
+ view_menu.add_action(hide_action_tabs_action);
+ view_menu.add_action(open_locator_action);
+ view_menu.add_separator();
+ view_menu.add_action(*m_add_editor_action);
+ view_menu.add_action(*m_remove_current_editor_action);
+ view_menu.add_action(*m_add_terminal_action);
+ view_menu.add_action(*m_remove_current_terminal_action);
+}
+
+void HackStudioWidget::create_help_menubar(GUI::MenuBar& menubar)
+{
+ auto& help_menu = menubar.add_menu("Help");
+ help_menu.add_action(GUI::CommonActions::make_about_action("Hack Studio", GUI::Icon::default_icon("app-hack-studio"), window()));
+}
+
+NonnullRefPtr<GUI::Action> HackStudioWidget::create_stop_action()
+{
+ auto action = GUI::Action::create("Stop", Gfx::Bitmap::load_from_file("/res/icons/16x16/program-stop.png"), [this](auto&) {
+ m_terminal_wrapper->kill_running_command();
+ });
+
+ action->set_enabled(false);
+ return action;
+}
+
+void HackStudioWidget::initialize_menubar(GUI::MenuBar& menubar)
+{
+ create_app_menubar(menubar);
+ create_project_menubar(menubar);
+ create_edit_menubar(menubar);
+ create_build_menubar(menubar);
+ create_view_menubar(menubar);
+ create_help_menubar(menubar);
+}
+
+HackStudioWidget::~HackStudioWidget()
+{
+ if (!m_debugger_thread.is_null()) {
+ Debugger::the().set_requested_debugger_action(Debugger::DebuggerAction::Exit);
+ dbgln("Waiting for debugger thread to terminate");
+ auto rc = m_debugger_thread->join();
+ if (rc.is_error()) {
+ warnln("pthread_join: {}", strerror(rc.error().value()));
+ dbgln("error joining debugger thread");
+ }
+ }
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/HackStudioWidget.h b/Userland/DevTools/HackStudio/HackStudioWidget.h
new file mode 100644
index 0000000000..29ef3701e4
--- /dev/null
+++ b/Userland/DevTools/HackStudio/HackStudioWidget.h
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * Copyright (c) 2020, the SerenityOS developers
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Debugger/DebugInfoWidget.h"
+#include "Debugger/DisassemblyWidget.h"
+#include "EditorWrapper.h"
+#include "FindInFilesWidget.h"
+#include "FormEditorWidget.h"
+#include "Git/DiffViewer.h"
+#include "Git/GitWidget.h"
+#include "Locator.h"
+#include "Project.h"
+#include "ProjectFile.h"
+#include "TerminalWrapper.h"
+#include <LibGUI/ScrollBar.h>
+#include <LibGUI/Splitter.h>
+#include <LibGUI/Widget.h>
+#include <LibThread/Thread.h>
+
+namespace HackStudio {
+
+class HackStudioWidget : public GUI::Widget {
+ C_OBJECT(HackStudioWidget)
+
+public:
+ virtual ~HackStudioWidget() override;
+ void open_file(const String& filename);
+
+ Vector<String> selected_file_names() const;
+
+ void update_actions();
+ Project& project();
+ GUI::TextEditor& current_editor();
+ EditorWrapper& current_editor_wrapper();
+ void set_current_editor_wrapper(RefPtr<EditorWrapper>);
+
+ String currently_open_file() const { return m_currently_open_file; }
+ void initialize_menubar(GUI::MenuBar&);
+
+private:
+ static String get_full_path_of_serenity_source(const String& file);
+
+ HackStudioWidget(const String& path_to_project);
+ void open_project(const String& root_path);
+
+ enum class EditMode {
+ Text,
+ Form,
+ Diff,
+ };
+
+ void set_edit_mode(EditMode);
+
+ NonnullRefPtr<GUI::Menu> create_project_tree_view_context_menu();
+ NonnullRefPtr<GUI::Action> create_new_action();
+ NonnullRefPtr<GUI::Action> create_open_selected_action();
+ NonnullRefPtr<GUI::Action> create_delete_action();
+ NonnullRefPtr<GUI::Action> create_switch_to_next_editor_action();
+ NonnullRefPtr<GUI::Action> create_switch_to_previous_editor_action();
+ NonnullRefPtr<GUI::Action> create_remove_current_editor_action();
+ NonnullRefPtr<GUI::Action> create_open_action();
+ NonnullRefPtr<GUI::Action> create_save_action();
+ NonnullRefPtr<GUI::Action> create_add_editor_action();
+ NonnullRefPtr<GUI::Action> create_add_terminal_action();
+ NonnullRefPtr<GUI::Action> create_remove_current_terminal_action();
+ NonnullRefPtr<GUI::Action> create_debug_action();
+ NonnullRefPtr<GUI::Action> create_build_action();
+ NonnullRefPtr<GUI::Action> create_run_action();
+ NonnullRefPtr<GUI::Action> create_stop_action();
+
+ void add_new_editor(GUI::Widget& parent);
+ NonnullRefPtr<EditorWrapper> get_editor_of_file(const String& file_name);
+ String get_project_executable_path() const;
+
+ void on_action_tab_change();
+ void reveal_action_tab(GUI::Widget&);
+ void initialize_debugger();
+
+ void create_project_tree_view(GUI::Widget& parent);
+ void create_open_files_view(GUI::Widget& parent);
+ void create_form_editor(GUI::Widget& parent);
+ void create_toolbar(GUI::Widget& parent);
+ void create_action_tab(GUI::Widget& parent);
+ void create_app_menubar(GUI::MenuBar&);
+ void create_project_menubar(GUI::MenuBar&);
+ void create_edit_menubar(GUI::MenuBar&);
+ void create_build_menubar(GUI::MenuBar&);
+ void create_view_menubar(GUI::MenuBar&);
+ void create_help_menubar(GUI::MenuBar&);
+
+ void run(TerminalWrapper& wrapper);
+ void build(TerminalWrapper& wrapper);
+
+ void hide_action_tabs();
+
+ NonnullRefPtrVector<EditorWrapper> m_all_editor_wrappers;
+ RefPtr<EditorWrapper> m_current_editor_wrapper;
+
+ // FIXME: This doesn't seem compatible with multiple split editors
+ String m_currently_open_file;
+
+ HashMap<String, NonnullRefPtr<ProjectFile>> m_open_files;
+ Vector<String> m_open_files_vector; // NOTE: This contains the keys from m_open_files
+
+ OwnPtr<Project> m_project;
+
+ RefPtr<GUI::TreeView> m_project_tree_view;
+ RefPtr<GUI::ListView> m_open_files_view;
+ RefPtr<GUI::VerticalSplitter> m_right_hand_splitter;
+ RefPtr<GUI::StackWidget> m_right_hand_stack;
+ RefPtr<GUI::Splitter> m_editors_splitter;
+ RefPtr<GUI::Widget> m_form_inner_container;
+ RefPtr<FormEditorWidget> m_form_editor_widget;
+ RefPtr<GUI::TreeView> m_form_widget_tree_view;
+ RefPtr<DiffViewer> m_diff_viewer;
+ RefPtr<GitWidget> m_git_widget;
+ RefPtr<GUI::Menu> m_project_tree_view_context_menu;
+ RefPtr<GUI::TabWidget> m_action_tab_widget;
+ RefPtr<TerminalWrapper> m_terminal_wrapper;
+ RefPtr<Locator> m_locator;
+ RefPtr<FindInFilesWidget> m_find_in_files_widget;
+ RefPtr<DebugInfoWidget> m_debug_info_widget;
+ RefPtr<DisassemblyWidget> m_disassembly_widget;
+ RefPtr<LibThread::Thread> m_debugger_thread;
+ RefPtr<EditorWrapper> m_current_editor_in_execution;
+
+ RefPtr<GUI::Action> m_new_action;
+ RefPtr<GUI::Action> m_open_selected_action;
+ RefPtr<GUI::Action> m_delete_action;
+ RefPtr<GUI::Action> m_switch_to_next_editor;
+ RefPtr<GUI::Action> m_switch_to_previous_editor;
+ RefPtr<GUI::Action> m_remove_current_editor_action;
+ RefPtr<GUI::Action> m_open_action;
+ RefPtr<GUI::Action> m_save_action;
+ RefPtr<GUI::Action> m_add_editor_action;
+ RefPtr<GUI::Action> m_add_terminal_action;
+ RefPtr<GUI::Action> m_remove_current_terminal_action;
+ RefPtr<GUI::Action> m_stop_action;
+ RefPtr<GUI::Action> m_debug_action;
+ RefPtr<GUI::Action> m_build_action;
+ RefPtr<GUI::Action> m_run_action;
+};
+}
diff --git a/Userland/DevTools/HackStudio/Language.h b/Userland/DevTools/HackStudio/Language.h
new file mode 100644
index 0000000000..d763a8ac8a
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Language.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace HackStudio {
+enum class Language {
+ Unknown,
+ Cpp,
+ JavaScript,
+ GML,
+ Ini,
+ Shell,
+};
+}
diff --git a/Userland/DevTools/HackStudio/LanguageClient.cpp b/Userland/DevTools/HackStudio/LanguageClient.cpp
new file mode 100644
index 0000000000..a10df6d4b6
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageClient.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "LanguageClient.h"
+#include <AK/String.h>
+#include <AK/Vector.h>
+
+namespace HackStudio {
+
+void ServerConnection::handle(const Messages::LanguageClient::AutoCompleteSuggestions& message)
+{
+ if (m_language_client)
+ m_language_client->provide_autocomplete_suggestions(message.suggestions());
+}
+
+void LanguageClient::open_file(const String& path, int fd)
+{
+ m_connection.post_message(Messages::LanguageServer::FileOpened(path, fd));
+}
+
+void LanguageClient::set_file_content(const String& path, const String& content)
+{
+ m_connection.post_message(Messages::LanguageServer::SetFileContent(path, content));
+}
+
+void LanguageClient::insert_text(const String& path, const String& text, size_t line, size_t column)
+{
+ m_connection.post_message(Messages::LanguageServer::FileEditInsertText(path, text, line, column));
+}
+
+void LanguageClient::remove_text(const String& path, size_t from_line, size_t from_column, size_t to_line, size_t to_column)
+{
+ m_connection.post_message(Messages::LanguageServer::FileEditRemoveText(path, from_line, from_column, to_line, to_column));
+}
+
+void LanguageClient::request_autocomplete(const String& path, size_t cursor_line, size_t cursor_column)
+{
+ m_connection.post_message(Messages::LanguageServer::AutoCompleteSuggestions(path, cursor_line, cursor_column));
+}
+
+void LanguageClient::provide_autocomplete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>& suggestions)
+{
+ if (on_autocomplete_suggestions)
+ on_autocomplete_suggestions(suggestions);
+
+ // Otherwise, drop it on the floor :shrug:
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/LanguageClient.h b/Userland/DevTools/HackStudio/LanguageClient.h
new file mode 100644
index 0000000000..bf294a7ea6
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageClient.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "AutoCompleteResponse.h"
+#include <AK/Forward.h>
+#include <AK/LexicalPath.h>
+#include <AK/Types.h>
+#include <LibIPC/ServerConnection.h>
+
+#include <DevTools/HackStudio/LanguageServers/LanguageClientEndpoint.h>
+#include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h>
+
+namespace HackStudio {
+
+class LanguageClient;
+
+class ServerConnection
+ : public IPC::ServerConnection<LanguageClientEndpoint, LanguageServerEndpoint>
+ , public LanguageClientEndpoint {
+public:
+ ServerConnection(const StringView& socket)
+ : IPC::ServerConnection<LanguageClientEndpoint, LanguageServerEndpoint>(*this, socket)
+ {
+ }
+
+ void attach(LanguageClient& client)
+ {
+ m_language_client = &client;
+ }
+
+ void detach()
+ {
+ m_language_client = nullptr;
+ }
+
+ virtual void handshake() override
+ {
+ auto response = send_sync<Messages::LanguageServer::Greet>();
+ set_my_client_id(response->client_id());
+ }
+
+ template<typename ConcreteType>
+ static NonnullRefPtr<ServerConnection> get_or_create(const String& project_path)
+ {
+ static HashMap<String, NonnullRefPtr<ConcreteType>> s_instances_for_projects;
+ auto key = LexicalPath { project_path }.string();
+ if (auto instance = s_instances_for_projects.get(key); instance.has_value())
+ return *instance.value();
+
+ auto connection = ConcreteType::construct();
+ connection->handshake();
+ s_instances_for_projects.set(key, *connection);
+ return *connection;
+ }
+
+protected:
+ virtual void handle(const Messages::LanguageClient::AutoCompleteSuggestions&) override;
+
+ LanguageClient* m_language_client { nullptr };
+};
+
+class LanguageClient {
+public:
+ explicit LanguageClient(NonnullRefPtr<ServerConnection>&& connection)
+ : m_connection(*connection)
+ , m_server_connection(move(connection))
+ {
+ m_connection.attach(*this);
+ }
+
+ virtual ~LanguageClient()
+ {
+ m_connection.detach();
+ }
+
+ virtual void open_file(const String& path, int fd);
+ virtual void set_file_content(const String& path, const String& content);
+ virtual void insert_text(const String& path, const String& text, size_t line, size_t column);
+ virtual void remove_text(const String& path, size_t from_line, size_t from_column, size_t to_line, size_t to_column);
+ virtual void request_autocomplete(const String& path, size_t cursor_line, size_t cursor_column);
+
+ void provide_autocomplete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>&);
+
+ Function<void(Vector<GUI::AutocompleteProvider::Entry>)> on_autocomplete_suggestions;
+
+private:
+ ServerConnection& m_connection;
+ NonnullRefPtr<ServerConnection> m_server_connection;
+};
+
+template<typename ServerConnectionT>
+static inline NonnullOwnPtr<LanguageClient> get_language_client(const String& project_path)
+{
+ return make<LanguageClient>(ServerConnection::get_or_create<ServerConnectionT>(project_path));
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/LanguageClients/CMakeLists.txt b/Userland/DevTools/HackStudio/LanguageClients/CMakeLists.txt
new file mode 100644
index 0000000000..e06c45f98c
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageClients/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(GENERATED_SOURCES
+ ../../LanguageServers/LanguageServerEndpoint.h
+ ../../LanguageServers/LanguageClientEndpoint.h
+)
diff --git a/Userland/DevTools/HackStudio/LanguageClients/ServerConnections.h b/Userland/DevTools/HackStudio/LanguageClients/ServerConnections.h
new file mode 100644
index 0000000000..bcebdb419e
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageClients/ServerConnections.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "../LanguageClient.h"
+#include <AK/LexicalPath.h>
+#include <DevTools/HackStudio/LanguageServers/LanguageClientEndpoint.h>
+#include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h>
+#include <LibIPC/ServerConnection.h>
+
+#define LANGUAGE_CLIENT(namespace_, socket_name) \
+ namespace namespace_ { \
+ class ServerConnection : public HackStudio::ServerConnection { \
+ C_OBJECT(ServerConnection) \
+ private: \
+ ServerConnection() \
+ : HackStudio::ServerConnection("/tmp/portal/language/" #socket_name) \
+ { \
+ } \
+ }; \
+ }
+
+namespace LanguageClients {
+
+LANGUAGE_CLIENT(Cpp, cpp)
+LANGUAGE_CLIENT(Shell, shell)
+
+}
+
+#undef LANGUAGE_CLIENT
diff --git a/Userland/DevTools/HackStudio/LanguageServers/CMakeLists.txt b/Userland/DevTools/HackStudio/LanguageServers/CMakeLists.txt
new file mode 100644
index 0000000000..afcc0118d0
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/CMakeLists.txt
@@ -0,0 +1,5 @@
+compile_ipc(LanguageServer.ipc LanguageServerEndpoint.h)
+compile_ipc(LanguageClient.ipc LanguageClientEndpoint.h)
+
+add_subdirectory(Cpp)
+add_subdirectory(Shell)
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp
new file mode 100644
index 0000000000..a6fae1c425
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "AutoComplete.h"
+#include <AK/HashTable.h>
+#include <LibCpp/Lexer.h>
+
+// #define DEBUG_AUTOCOMPLETE
+
+namespace LanguageServers::Cpp {
+
+Vector<GUI::AutocompleteProvider::Entry> AutoComplete::get_suggestions(const String& code, const GUI::TextPosition& autocomplete_position)
+{
+ auto lines = code.split('\n', true);
+ Cpp::Lexer lexer(code);
+ auto tokens = lexer.lex();
+
+ auto index_of_target_token = token_in_position(tokens, autocomplete_position);
+ if (!index_of_target_token.has_value())
+ return {};
+
+ auto suggestions = identifier_prefixes(lines, tokens, index_of_target_token.value());
+
+#ifdef DEBUG_AUTOCOMPLETE
+ for (auto& suggestion : suggestions) {
+ dbgln("suggestion: {}", suggestion.completion);
+ }
+#endif
+
+ return suggestions;
+}
+
+StringView AutoComplete::text_of_token(const Vector<String>& lines, const Cpp::Token& token)
+{
+ ASSERT(token.m_start.line == token.m_end.line);
+ ASSERT(token.m_start.column <= token.m_end.column);
+ return lines[token.m_start.line].substring_view(token.m_start.column, token.m_end.column - token.m_start.column + 1);
+}
+
+Optional<size_t> AutoComplete::token_in_position(const Vector<Cpp::Token>& tokens, const GUI::TextPosition& position)
+{
+ for (size_t token_index = 0; token_index < tokens.size(); ++token_index) {
+ auto& token = tokens[token_index];
+ if (token.m_start.line != token.m_end.line)
+ continue;
+ if (token.m_start.line != position.line())
+ continue;
+ if (token.m_start.column + 1 > position.column() || token.m_end.column + 1 < position.column())
+ continue;
+ return token_index;
+ }
+ return {};
+}
+
+Vector<GUI::AutocompleteProvider::Entry> AutoComplete::identifier_prefixes(const Vector<String>& lines, const Vector<Cpp::Token>& tokens, size_t target_token_index)
+{
+ auto partial_input = text_of_token(lines, tokens[target_token_index]);
+ Vector<GUI::AutocompleteProvider::Entry> suggestions;
+
+ HashTable<String> suggestions_lookup; // To avoid duplicate results
+
+ for (size_t i = 0; i < target_token_index; ++i) {
+ auto& token = tokens[i];
+ if (token.m_type != Cpp::Token::Type::Identifier)
+ continue;
+ auto text = text_of_token(lines, token);
+ if (text.starts_with(partial_input) && suggestions_lookup.set(text) == AK::HashSetResult::InsertedNewEntry) {
+ suggestions.append({ text, partial_input.length(), GUI::AutocompleteProvider::CompletionKind::Identifier });
+ }
+ }
+ return suggestions;
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h
new file mode 100644
index 0000000000..b4096bd793
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/AutoComplete.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <DevTools/HackStudio/AutoCompleteResponse.h>
+#include <LibCpp/Lexer.h>
+#include <LibGUI/TextPosition.h>
+
+namespace LanguageServers::Cpp {
+
+using namespace ::Cpp;
+
+class AutoComplete {
+public:
+ AutoComplete() = delete;
+
+ static Vector<GUI::AutocompleteProvider::Entry> get_suggestions(const String& code, const GUI::TextPosition& autocomplete_position);
+
+private:
+ static Optional<size_t> token_in_position(const Vector<Cpp::Token>&, const GUI::TextPosition&);
+ static StringView text_of_token(const Vector<String>& lines, const Cpp::Token&);
+ static Vector<GUI::AutocompleteProvider::Entry> identifier_prefixes(const Vector<String>& lines, const Vector<Cpp::Token>&, size_t target_token_index);
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt b/Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt
new file mode 100644
index 0000000000..de9569d8ca
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(SOURCES
+ ClientConnection.cpp
+ main.cpp
+ AutoComplete.cpp
+)
+
+set(GENERATED_SOURCES
+ ../LanguageServerEndpoint.h
+ ../LanguageClientEndpoint.h)
+
+serenity_bin(CppLanguageServer)
+
+# We link with LibGUI because we use GUI::TextDocument to update
+# the content of files according to the edit actions we receive over IPC.
+target_link_libraries(CppLanguageServer LibIPC LibCpp LibGUI)
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp
new file mode 100644
index 0000000000..7952696b60
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ClientConnection.h"
+#include "AutoComplete.h"
+#include <AK/HashMap.h>
+#include <LibCore/File.h>
+#include <LibGUI/TextDocument.h>
+
+// #define DEBUG_CPP_LANGUAGE_SERVER
+// #define DEBUG_FILE_CONTENT
+
+namespace LanguageServers::Cpp {
+
+static HashMap<int, RefPtr<ClientConnection>> s_connections;
+
+ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> socket, int client_id)
+ : IPC::ClientConnection<LanguageClientEndpoint, LanguageServerEndpoint>(*this, move(socket), client_id)
+{
+ s_connections.set(client_id, *this);
+}
+
+ClientConnection::~ClientConnection()
+{
+}
+
+void ClientConnection::die()
+{
+ s_connections.remove(client_id());
+ exit(0);
+}
+
+OwnPtr<Messages::LanguageServer::GreetResponse> ClientConnection::handle(const Messages::LanguageServer::Greet&)
+{
+ return make<Messages::LanguageServer::GreetResponse>(client_id());
+}
+
+class DefaultDocumentClient final : public GUI::TextDocument::Client {
+public:
+ virtual ~DefaultDocumentClient() override = default;
+ virtual void document_did_append_line() override {};
+ virtual void document_did_insert_line(size_t) override {};
+ virtual void document_did_remove_line(size_t) override {};
+ virtual void document_did_remove_all_lines() override {};
+ virtual void document_did_change() override {};
+ virtual void document_did_set_text() override {};
+ virtual void document_did_set_cursor(const GUI::TextPosition&) override {};
+
+ virtual bool is_automatic_indentation_enabled() const override { return false; }
+ virtual int soft_tab_width() const override { return 4; }
+};
+
+static DefaultDocumentClient s_default_document_client;
+
+void ClientConnection::handle(const Messages::LanguageServer::FileOpened& message)
+{
+ auto file = Core::File::construct(this);
+ if (!file->open(message.file().fd(), Core::IODevice::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes)) {
+ errno = file->error();
+ perror("open");
+ dbgln("Failed to open project file");
+ return;
+ }
+ auto content = file->read_all();
+ StringView content_view(content);
+ auto document = GUI::TextDocument::create(&s_default_document_client);
+ document->set_text(content_view);
+ m_open_files.set(message.file_name(), document);
+#ifdef DEBUG_FILE_CONTENT
+ dbg() << document->text();
+#endif
+}
+
+void ClientConnection::handle(const Messages::LanguageServer::FileEditInsertText& message)
+{
+#ifdef DEBUG_CPP_LANGUAGE_SERVER
+ dbgln("InsertText for file: {}", message.file_name());
+ dbgln("Text: {}", message.text());
+ dbgln("[{}:{}]", message.start_line(), message.start_column());
+#endif
+ auto document = document_for(message.file_name());
+ if (!document) {
+ dbgln("file {} has not been opened", message.file_name());
+ return;
+ }
+ GUI::TextPosition start_position { (size_t)message.start_line(), (size_t)message.start_column() };
+ document->insert_at(start_position, message.text(), &s_default_document_client);
+#ifdef DEBUG_FILE_CONTENT
+ dbgln("{}", document->text());
+#endif
+}
+
+void ClientConnection::handle(const Messages::LanguageServer::FileEditRemoveText& message)
+{
+#ifdef DEBUG_CPP_LANGUAGE_SERVER
+ dbgln("RemoveText for file: {}", message.file_name());
+ dbgln("[{}:{} - {}:{}]", message.start_line(), message.start_column(), message.end_line(), message.end_column());
+#endif
+ auto document = document_for(message.file_name());
+ if (!document) {
+ dbgln("file {} has not been opened", message.file_name());
+ return;
+ }
+ GUI::TextPosition start_position { (size_t)message.start_line(), (size_t)message.start_column() };
+ GUI::TextRange range {
+ GUI::TextPosition { (size_t)message.start_line(),
+ (size_t)message.start_column() },
+ GUI::TextPosition { (size_t)message.end_line(),
+ (size_t)message.end_column() }
+ };
+
+ document->remove(range);
+#ifdef DEBUG_FILE_CONTENT
+ dbgln("{}", document->text());
+#endif
+}
+
+void ClientConnection::handle(const Messages::LanguageServer::AutoCompleteSuggestions& message)
+{
+#ifdef DEBUG_CPP_LANGUAGE_SERVER
+ dbgln("AutoCompleteSuggestions for: {} {}:{}", message.file_name(), message.cursor_line(), message.cursor_column());
+#endif
+
+ auto document = document_for(message.file_name());
+ if (!document) {
+ dbgln("file {} has not been opened", message.file_name());
+ return;
+ }
+
+ auto suggestions = AutoComplete::get_suggestions(document->text(), { (size_t)message.cursor_line(), (size_t)max(message.cursor_column(), message.cursor_column() - 1) });
+ post_message(Messages::LanguageClient::AutoCompleteSuggestions(move(suggestions)));
+}
+
+RefPtr<GUI::TextDocument> ClientConnection::document_for(const String& file_name)
+{
+ auto document_optional = m_open_files.get(file_name);
+ if (!document_optional.has_value())
+ return nullptr;
+
+ return document_optional.value();
+}
+
+void ClientConnection::handle(const Messages::LanguageServer::SetFileContent& message)
+{
+ auto document = document_for(message.file_name());
+ if (!document) {
+ dbgln("file {} has not been opened", message.file_name());
+ return;
+ }
+ auto content = message.content();
+ document->set_text(content.view());
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h
new file mode 100644
index 0000000000..d0a7b4558c
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/LexicalPath.h>
+#include <DevTools/HackStudio/AutoCompleteResponse.h>
+#include <LibGUI/TextDocument.h>
+#include <LibIPC/ClientConnection.h>
+
+#include <DevTools/HackStudio/LanguageServers/LanguageClientEndpoint.h>
+#include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h>
+
+namespace LanguageServers::Cpp {
+
+class ClientConnection final
+ : public IPC::ClientConnection<LanguageClientEndpoint, LanguageServerEndpoint>
+ , public LanguageServerEndpoint {
+ C_OBJECT(ClientConnection);
+
+public:
+ explicit ClientConnection(NonnullRefPtr<Core::LocalSocket>, int client_id);
+ ~ClientConnection() override;
+
+ virtual void die() override;
+
+private:
+ virtual OwnPtr<Messages::LanguageServer::GreetResponse> handle(const Messages::LanguageServer::Greet&) override;
+ virtual void handle(const Messages::LanguageServer::FileOpened&) override;
+ virtual void handle(const Messages::LanguageServer::FileEditInsertText&) override;
+ virtual void handle(const Messages::LanguageServer::FileEditRemoveText&) override;
+ virtual void handle(const Messages::LanguageServer::SetFileContent&) override;
+ virtual void handle(const Messages::LanguageServer::AutoCompleteSuggestions&) override;
+
+ RefPtr<GUI::TextDocument> document_for(const String& file_name);
+
+ HashMap<String, NonnullRefPtr<GUI::TextDocument>> m_open_files;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp
new file mode 100644
index 0000000000..197fd91f27
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LexicalPath.h>
+#include <DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/File.h>
+#include <LibCore/LocalServer.h>
+#include <LibIPC/ClientConnection.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+int main(int, char**)
+{
+ Core::EventLoop event_loop;
+ if (pledge("stdio unix recvfd", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ auto socket = Core::LocalSocket::take_over_accepted_socket_from_system_server();
+ IPC::new_client_connection<LanguageServers::Cpp::ClientConnection>(socket.release_nonnull(), 1);
+ if (pledge("stdio recvfd", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+ if (unveil(nullptr, nullptr) < 0) {
+ perror("unveil");
+ return 1;
+ }
+ return event_loop.exec();
+}
diff --git a/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc b/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc
new file mode 100644
index 0000000000..31b5d5b2c5
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/LanguageClient.ipc
@@ -0,0 +1,4 @@
+endpoint LanguageClient = 8002
+{
+ AutoCompleteSuggestions(Vector<GUI::AutocompleteProvider::Entry> suggestions) =|
+}
diff --git a/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc
new file mode 100644
index 0000000000..de16fa14ec
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc
@@ -0,0 +1,11 @@
+endpoint LanguageServer = 8001
+{
+ Greet() => (i32 client_id)
+
+ FileOpened(String file_name, IPC::File file) =|
+ FileEditInsertText(String file_name, String text, i32 start_line, i32 start_column) =|
+ FileEditRemoveText(String file_name, i32 start_line, i32 start_column, i32 end_line, i32 end_column) =|
+ SetFileContent(String file_name, String content) =|
+
+ AutoCompleteSuggestions(String file_name, i32 cursor_line, i32 cursor_column) =|
+}
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.cpp b/Userland/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.cpp
new file mode 100644
index 0000000000..89df78365a
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "AutoComplete.h"
+#include <AK/HashTable.h>
+#include <LibLine/SuggestionManager.h>
+#include <Shell/AST.h>
+#include <Shell/Parser.h>
+#include <Shell/Shell.h>
+
+// #define DEBUG_AUTOCOMPLETE
+
+namespace LanguageServers::Shell {
+
+Vector<GUI::AutocompleteProvider::Entry> AutoComplete::get_suggestions(const String& code, size_t offset)
+{
+ // FIXME: No need to reparse this every time!
+ auto ast = ::Shell::Parser { code }.parse();
+ if (!ast)
+ return {};
+
+#ifdef DEBUG_AUTOCOMPLETE
+ dbgln("Complete '{}'", code);
+ ast->dump(1);
+ dbgln("At offset {}", offset);
+#endif
+
+ auto result = ast->complete_for_editor(m_shell, offset);
+ Vector<GUI::AutocompleteProvider::Entry> completions;
+ for (auto& entry : result) {
+#ifdef DEBUG_AUTOCOMPLETE
+ dbgln("Suggestion: '{}' starting at {}", entry.text_string, entry.input_offset);
+#endif
+ completions.append({ entry.text_string, entry.input_offset });
+ }
+
+ return completions;
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.h b/Userland/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.h
new file mode 100644
index 0000000000..fece37dcb7
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <DevTools/HackStudio/AutoCompleteResponse.h>
+#include <LibGUI/TextPosition.h>
+#include <Shell/Shell.h>
+
+namespace LanguageServers::Shell {
+
+class AutoComplete {
+public:
+ AutoComplete()
+ : m_shell(::Shell::Shell::construct())
+ {
+ }
+
+ Vector<GUI::AutocompleteProvider::Entry> get_suggestions(const String& code, size_t autocomplete_position);
+
+private:
+ NonnullRefPtr<::Shell::Shell> m_shell;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/CMakeLists.txt b/Userland/DevTools/HackStudio/LanguageServers/Shell/CMakeLists.txt
new file mode 100644
index 0000000000..144a4526a3
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Shell/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(SOURCES
+ ClientConnection.cpp
+ main.cpp
+ AutoComplete.cpp
+)
+
+set(GENERATED_SOURCES
+ ../LanguageServerEndpoint.h
+ ../LanguageClientEndpoint.h)
+
+serenity_bin(ShellLanguageServer)
+
+# We link with LibGUI because we use GUI::TextDocument to update
+# the content of files according to the edit actions we receive over IPC.
+target_link_libraries(ShellLanguageServer LibIPC LibShell LibGUI)
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.cpp b/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.cpp
new file mode 100644
index 0000000000..794085f618
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ClientConnection.h"
+#include "AutoComplete.h"
+#include <AK/HashMap.h>
+#include <LibCore/File.h>
+#include <LibGUI/TextDocument.h>
+
+// #define DEBUG_SH_LANGUAGE_SERVER
+// #define DEBUG_FILE_CONTENT
+
+namespace LanguageServers::Shell {
+
+static HashMap<int, RefPtr<ClientConnection>> s_connections;
+
+ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> socket, int client_id)
+ : IPC::ClientConnection<LanguageClientEndpoint, LanguageServerEndpoint>(*this, move(socket), client_id)
+{
+ s_connections.set(client_id, *this);
+}
+
+ClientConnection::~ClientConnection()
+{
+}
+
+void ClientConnection::die()
+{
+ s_connections.remove(client_id());
+ exit(0);
+}
+
+OwnPtr<Messages::LanguageServer::GreetResponse> ClientConnection::handle(const Messages::LanguageServer::Greet&)
+{
+ return make<Messages::LanguageServer::GreetResponse>(client_id());
+}
+
+class DefaultDocumentClient final : public GUI::TextDocument::Client {
+public:
+ virtual ~DefaultDocumentClient() override = default;
+ virtual void document_did_append_line() override {};
+ virtual void document_did_insert_line(size_t) override {};
+ virtual void document_did_remove_line(size_t) override {};
+ virtual void document_did_remove_all_lines() override {};
+ virtual void document_did_change() override {};
+ virtual void document_did_set_text() override {};
+ virtual void document_did_set_cursor(const GUI::TextPosition&) override {};
+
+ virtual bool is_automatic_indentation_enabled() const override { return false; }
+ virtual int soft_tab_width() const override { return 4; }
+};
+
+static DefaultDocumentClient s_default_document_client;
+
+void ClientConnection::handle(const Messages::LanguageServer::FileOpened& message)
+{
+ auto file = Core::File::construct(this);
+ if (!file->open(message.file().fd(), Core::IODevice::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes)) {
+ errno = file->error();
+ perror("open");
+ dbgln("Failed to open project file");
+ return;
+ }
+ auto content = file->read_all();
+ StringView content_view(content);
+ auto document = GUI::TextDocument::create(&s_default_document_client);
+ document->set_text(content_view);
+ m_open_files.set(message.file_name(), document);
+#ifdef DEBUG_FILE_CONTENT
+ dbg() << document->text();
+#endif
+}
+
+void ClientConnection::handle(const Messages::LanguageServer::FileEditInsertText& message)
+{
+#ifdef DEBUG_SH_LANGUAGE_SERVER
+ dbgln("InsertText for file: {}", message.file_name());
+ dbgln("Text: {}", message.text());
+ dbgln("[{}:{}]", message.start_line(), message.start_column());
+#endif
+ auto document = document_for(message.file_name());
+ if (!document) {
+ dbgln("file {} has not been opened", message.file_name());
+ return;
+ }
+ GUI::TextPosition start_position { (size_t)message.start_line(), (size_t)message.start_column() };
+ document->insert_at(start_position, message.text(), &s_default_document_client);
+#ifdef DEBUG_FILE_CONTENT
+ dbgln("{}", document->text());
+#endif
+}
+
+void ClientConnection::handle(const Messages::LanguageServer::FileEditRemoveText& message)
+{
+#ifdef DEBUG_SH_LANGUAGE_SERVER
+ dbgln("RemoveText for file: {}", message.file_name());
+ dbgln("[{}:{} - {}:{}]", message.start_line(), message.start_column(), message.end_line(), message.end_column());
+#endif
+ auto document = document_for(message.file_name());
+ if (!document) {
+ dbgln("file {} has not been opened", message.file_name());
+ return;
+ }
+ GUI::TextPosition start_position { (size_t)message.start_line(), (size_t)message.start_column() };
+ GUI::TextRange range {
+ GUI::TextPosition { (size_t)message.start_line(),
+ (size_t)message.start_column() },
+ GUI::TextPosition { (size_t)message.end_line(),
+ (size_t)message.end_column() }
+ };
+
+ document->remove(range);
+#ifdef DEBUG_FILE_CONTENT
+ dbg() << document->text();
+#endif
+}
+
+void ClientConnection::handle(const Messages::LanguageServer::AutoCompleteSuggestions& message)
+{
+#ifdef DEBUG_SH_LANGUAGE_SERVER
+ dbgln("AutoCompleteSuggestions for: {} {}:{}", message.file_name(), message.cursor_line(), message.cursor_column());
+#endif
+
+ auto document = document_for(message.file_name());
+ if (!document) {
+ dbgln("file {} has not been opened", message.file_name());
+ return;
+ }
+
+ auto& lines = document->lines();
+ size_t offset = 0;
+
+ if (message.cursor_line() > 0) {
+ for (auto i = 0; i < message.cursor_line(); ++i)
+ offset += lines[i].length() + 1;
+ }
+ offset += message.cursor_column();
+
+ auto suggestions = m_autocomplete.get_suggestions(document->text(), offset);
+ post_message(Messages::LanguageClient::AutoCompleteSuggestions(move(suggestions)));
+}
+
+RefPtr<GUI::TextDocument> ClientConnection::document_for(const String& file_name)
+{
+ auto document_optional = m_open_files.get(file_name);
+ if (!document_optional.has_value())
+ return nullptr;
+
+ return document_optional.value();
+}
+
+void ClientConnection::handle(const Messages::LanguageServer::SetFileContent& message)
+{
+ auto document = document_for(message.file_name());
+ if (!document) {
+ dbgln("file {} has not been opened", message.file_name());
+ return;
+ }
+ auto content = message.content();
+ document->set_text(content.view());
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h b/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h
new file mode 100644
index 0000000000..bb7fc14bd9
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "AutoComplete.h"
+#include <AK/HashMap.h>
+#include <AK/LexicalPath.h>
+#include <DevTools/HackStudio/AutoCompleteResponse.h>
+#include <LibGUI/TextDocument.h>
+#include <LibIPC/ClientConnection.h>
+
+#include <DevTools/HackStudio/LanguageServers/LanguageClientEndpoint.h>
+#include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h>
+
+namespace LanguageServers::Shell {
+
+class ClientConnection final
+ : public IPC::ClientConnection<LanguageClientEndpoint, LanguageServerEndpoint>
+ , public LanguageServerEndpoint {
+ C_OBJECT(ClientConnection);
+
+public:
+ explicit ClientConnection(NonnullRefPtr<Core::LocalSocket>, int client_id);
+ ~ClientConnection() override;
+
+ virtual void die() override;
+
+private:
+ virtual OwnPtr<Messages::LanguageServer::GreetResponse> handle(const Messages::LanguageServer::Greet&) override;
+ virtual void handle(const Messages::LanguageServer::FileOpened&) override;
+ virtual void handle(const Messages::LanguageServer::FileEditInsertText&) override;
+ virtual void handle(const Messages::LanguageServer::FileEditRemoveText&) override;
+ virtual void handle(const Messages::LanguageServer::SetFileContent&) override;
+ virtual void handle(const Messages::LanguageServer::AutoCompleteSuggestions&) override;
+
+ RefPtr<GUI::TextDocument> document_for(const String& file_name);
+
+ HashMap<String, NonnullRefPtr<GUI::TextDocument>> m_open_files;
+
+ AutoComplete m_autocomplete;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/LanguageServers/Shell/main.cpp b/Userland/DevTools/HackStudio/LanguageServers/Shell/main.cpp
new file mode 100644
index 0000000000..90e3f62271
--- /dev/null
+++ b/Userland/DevTools/HackStudio/LanguageServers/Shell/main.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/LexicalPath.h>
+#include <DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/File.h>
+#include <LibCore/LocalServer.h>
+#include <LibIPC/ClientConnection.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+int main(int, char**)
+{
+ Core::EventLoop event_loop;
+ if (pledge("stdio unix rpath recvfd", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ auto socket = Core::LocalSocket::take_over_accepted_socket_from_system_server();
+ IPC::new_client_connection<LanguageServers::Shell::ClientConnection>(socket.release_nonnull(), 1);
+ if (pledge("stdio rpath recvfd", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+ if (unveil("/etc/passwd", "r") < 0) {
+ perror("unveil");
+ return 1;
+ }
+ if (unveil("/", "b") < 0) {
+ perror("unveil");
+ return 1;
+ }
+ if (unveil(nullptr, nullptr) < 0) {
+ perror("unveil");
+ return 1;
+ }
+ return event_loop.exec();
+}
diff --git a/Userland/DevTools/HackStudio/Locator.cpp b/Userland/DevTools/HackStudio/Locator.cpp
new file mode 100644
index 0000000000..50c16fde1e
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Locator.cpp
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Locator.h"
+#include "HackStudio.h"
+#include "Project.h"
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/FileIconProvider.h>
+#include <LibGUI/TableView.h>
+#include <LibGUI/TextBox.h>
+#include <LibGUI/Window.h>
+
+namespace HackStudio {
+
+class LocatorSuggestionModel final : public GUI::Model {
+public:
+ explicit LocatorSuggestionModel(Vector<String>&& suggestions)
+ : m_suggestions(move(suggestions))
+ {
+ }
+
+ enum Column {
+ Icon,
+ Name,
+ __Column_Count,
+ };
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_suggestions.size(); }
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Column_Count; }
+ virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override
+ {
+ auto& suggestion = m_suggestions.at(index.row());
+ if (role == GUI::ModelRole::Display) {
+ if (index.column() == Column::Name)
+ return suggestion;
+ if (index.column() == Column::Icon)
+ return GUI::FileIconProvider::icon_for_path(suggestion);
+ }
+ return {};
+ }
+ virtual void update() override {};
+
+private:
+ Vector<String> m_suggestions;
+};
+
+Locator::Locator()
+{
+ set_layout<GUI::VerticalBoxLayout>();
+ set_fixed_height(20);
+ m_textbox = add<GUI::TextBox>();
+ m_textbox->on_change = [this] {
+ update_suggestions();
+ };
+ m_textbox->on_escape_pressed = [this] {
+ m_popup_window->hide();
+ };
+ m_textbox->on_up_pressed = [this] {
+ GUI::ModelIndex new_index = m_suggestion_view->selection().first();
+ if (new_index.is_valid())
+ new_index = m_suggestion_view->model()->index(new_index.row() - 1);
+ else
+ new_index = m_suggestion_view->model()->index(0);
+
+ if (m_suggestion_view->model()->is_valid(new_index)) {
+ m_suggestion_view->selection().set(new_index);
+ m_suggestion_view->scroll_into_view(new_index, Orientation::Vertical);
+ }
+ };
+ m_textbox->on_down_pressed = [this] {
+ GUI::ModelIndex new_index = m_suggestion_view->selection().first();
+ if (new_index.is_valid())
+ new_index = m_suggestion_view->model()->index(new_index.row() + 1);
+ else
+ new_index = m_suggestion_view->model()->index(0);
+
+ if (m_suggestion_view->model()->is_valid(new_index)) {
+ m_suggestion_view->selection().set(new_index);
+ m_suggestion_view->scroll_into_view(new_index, Orientation::Vertical);
+ }
+ };
+
+ m_textbox->on_return_pressed = [this] {
+ auto selected_index = m_suggestion_view->selection().first();
+ if (!selected_index.is_valid())
+ return;
+ open_suggestion(selected_index);
+ };
+
+ m_popup_window = GUI::Window::construct();
+ // FIXME: This is obviously not a tooltip window, but it's the closest thing to what we want atm.
+ m_popup_window->set_window_type(GUI::WindowType::Tooltip);
+ m_popup_window->set_rect(0, 0, 500, 200);
+
+ m_suggestion_view = m_popup_window->set_main_widget<GUI::TableView>();
+ m_suggestion_view->set_column_headers_visible(false);
+
+ m_suggestion_view->on_activation = [this](auto& index) {
+ open_suggestion(index);
+ };
+}
+
+Locator::~Locator()
+{
+}
+
+void Locator::open_suggestion(const GUI::ModelIndex& index)
+{
+ auto filename_index = m_suggestion_view->model()->index(index.row(), LocatorSuggestionModel::Column::Name);
+ auto filename = filename_index.data().to_string();
+ open_file(filename);
+ close();
+}
+
+void Locator::open()
+{
+ m_textbox->set_focus(true);
+ if (!m_textbox->text().is_empty()) {
+ m_textbox->select_all();
+ m_popup_window->show();
+ }
+}
+
+void Locator::close()
+{
+ m_popup_window->hide();
+}
+
+void Locator::update_suggestions()
+{
+ auto typed_text = m_textbox->text();
+ Vector<String> suggestions;
+ project().for_each_text_file([&](auto& file) {
+ if (file.name().contains(typed_text, CaseSensitivity::CaseInsensitive))
+ suggestions.append(file.name());
+ });
+ dbgln("I have {} suggestion(s):", suggestions.size());
+ for (auto& s : suggestions) {
+ dbgln(" {}", s);
+ }
+
+ bool has_suggestions = !suggestions.is_empty();
+
+ m_suggestion_view->set_model(adopt(*new LocatorSuggestionModel(move(suggestions))));
+
+ if (!has_suggestions)
+ m_suggestion_view->selection().clear();
+ else
+ m_suggestion_view->selection().set(m_suggestion_view->model()->index(0));
+
+ m_popup_window->move_to(screen_relative_rect().top_left().translated(0, -m_popup_window->height()));
+ dbgln("Popup rect: {}", m_popup_window->rect());
+ m_popup_window->show();
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/Locator.h b/Userland/DevTools/HackStudio/Locator.h
new file mode 100644
index 0000000000..e52eb02447
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Locator.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+namespace HackStudio {
+
+class Locator final : public GUI::Widget {
+ C_OBJECT(Locator)
+public:
+ virtual ~Locator() override;
+
+ void open();
+ void close();
+
+private:
+ void update_suggestions();
+ void open_suggestion(const GUI::ModelIndex&);
+
+ Locator();
+
+ RefPtr<GUI::TextBox> m_textbox;
+ RefPtr<GUI::Window> m_popup_window;
+ RefPtr<GUI::TableView> m_suggestion_view;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Project.cpp b/Userland/DevTools/HackStudio/Project.cpp
new file mode 100644
index 0000000000..f83f2e8940
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Project.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Project.h"
+#include "HackStudio.h"
+#include <LibCore/File.h>
+
+namespace HackStudio {
+
+Project::Project(const String& root_path)
+ : m_root_path(root_path)
+{
+ m_model = GUI::FileSystemModel::create(root_path, GUI::FileSystemModel::Mode::FilesAndDirectories);
+}
+
+Project::~Project()
+{
+}
+
+OwnPtr<Project> Project::open_with_root_path(const String& root_path)
+{
+ if (!Core::File::is_directory(root_path))
+ return {};
+ return adopt_own(*new Project(root_path));
+}
+
+template<typename Callback>
+static void traverse_model(const GUI::FileSystemModel& model, const GUI::ModelIndex& index, Callback callback)
+{
+ if (index.is_valid())
+ callback(index);
+ auto row_count = model.row_count(index);
+ if (!row_count)
+ return;
+ for (int row = 0; row < row_count; ++row) {
+ auto child_index = model.index(row, GUI::FileSystemModel::Column::Name, index);
+ traverse_model(model, child_index, callback);
+ }
+}
+
+void Project::for_each_text_file(Function<void(const ProjectFile&)> callback) const
+{
+ traverse_model(model(), {}, [&](auto& index) {
+ auto file = get_file(model().full_path(index));
+ if (file)
+ callback(*file);
+ });
+}
+
+RefPtr<ProjectFile> Project::get_file(const String& path) const
+{
+ for (auto& file : m_files) {
+ if (file.name() == path)
+ return file;
+ }
+ auto file = ProjectFile::construct_with_name(path);
+ m_files.append(file);
+ return file;
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/Project.h b/Userland/DevTools/HackStudio/Project.h
new file mode 100644
index 0000000000..6d3453e84e
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Project.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "ProjectFile.h"
+#include <AK/LexicalPath.h>
+#include <AK/Noncopyable.h>
+#include <AK/OwnPtr.h>
+#include <LibGUI/FileSystemModel.h>
+
+namespace HackStudio {
+
+class Project {
+ AK_MAKE_NONCOPYABLE(Project);
+ AK_MAKE_NONMOVABLE(Project);
+
+public:
+ ~Project();
+
+ static OwnPtr<Project> open_with_root_path(const String& root_path);
+
+ GUI::FileSystemModel& model() { return *m_model; }
+ const GUI::FileSystemModel& model() const { return *m_model; }
+ String name() const { return LexicalPath(m_root_path).basename(); }
+ String root_path() const { return m_root_path; }
+
+ RefPtr<ProjectFile> get_file(const String& path) const;
+
+ void for_each_text_file(Function<void(const ProjectFile&)>) const;
+
+private:
+ explicit Project(const String& root_path);
+
+ RefPtr<GUI::FileSystemModel> m_model;
+ mutable NonnullRefPtrVector<ProjectFile> m_files;
+
+ String m_root_path;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/ProjectFile.cpp b/Userland/DevTools/HackStudio/ProjectFile.cpp
new file mode 100644
index 0000000000..989632b480
--- /dev/null
+++ b/Userland/DevTools/HackStudio/ProjectFile.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ProjectFile.h"
+#include <LibCore/File.h>
+#include <string.h>
+
+namespace HackStudio {
+
+ProjectFile::ProjectFile(const String& name)
+ : m_name(name)
+{
+}
+
+GUI::TextDocument& ProjectFile::document() const
+{
+ if (!m_document) {
+ m_document = CodeDocument::create(m_name);
+ auto file_or_error = Core::File::open(m_name, Core::File::ReadOnly);
+ if (file_or_error.is_error()) {
+ warnln("Couldn't open '{}': {}", m_name, file_or_error.error());
+ // This is okay though, we'll just go with an empty document and create the file when saving.
+ } else {
+ auto& file = *file_or_error.value();
+ m_document->set_text(file.read_all());
+ }
+ }
+ return *m_document;
+}
+
+int ProjectFile::vertical_scroll_value() const
+{
+ return m_vertical_scroll_value;
+}
+
+void ProjectFile::vertical_scroll_value(int vertical_scroll_value)
+{
+ m_vertical_scroll_value = vertical_scroll_value;
+}
+
+int ProjectFile::horizontal_scroll_value() const
+{
+ return m_horizontal_scroll_value;
+}
+
+void ProjectFile::horizontal_scroll_value(int horizontal_scroll_value)
+{
+ m_horizontal_scroll_value = horizontal_scroll_value;
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/ProjectFile.h b/Userland/DevTools/HackStudio/ProjectFile.h
new file mode 100644
index 0000000000..c679cd79e0
--- /dev/null
+++ b/Userland/DevTools/HackStudio/ProjectFile.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "CodeDocument.h"
+#include <AK/ByteBuffer.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/RefCounted.h>
+#include <AK/String.h>
+
+namespace HackStudio {
+
+class ProjectFile : public RefCounted<ProjectFile> {
+public:
+ static NonnullRefPtr<ProjectFile> construct_with_name(const String& name)
+ {
+ return adopt(*new ProjectFile(name));
+ }
+
+ const String& name() const { return m_name; }
+
+ GUI::TextDocument& document() const;
+
+ int vertical_scroll_value() const;
+ void vertical_scroll_value(int);
+ int horizontal_scroll_value() const;
+ void horizontal_scroll_value(int);
+
+private:
+ explicit ProjectFile(const String& name);
+
+ String m_name;
+ mutable RefPtr<CodeDocument> m_document;
+ int m_vertical_scroll_value { 0 };
+ int m_horizontal_scroll_value { 0 };
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/TerminalWrapper.cpp b/Userland/DevTools/HackStudio/TerminalWrapper.cpp
new file mode 100644
index 0000000000..ef7c220c42
--- /dev/null
+++ b/Userland/DevTools/HackStudio/TerminalWrapper.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "TerminalWrapper.h"
+#include <AK/String.h>
+#include <LibCore/ConfigFile.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/MessageBox.h>
+#include <LibVT/TerminalWidget.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+namespace HackStudio {
+
+void TerminalWrapper::run_command(const String& command)
+{
+ if (m_pid != -1) {
+ GUI::MessageBox::show(window(),
+ "A command is already running in this TerminalWrapper",
+ "Can't run command",
+ GUI::MessageBox::Type::Error);
+ return;
+ }
+
+ int ptm_fd = posix_openpt(O_RDWR | O_CLOEXEC);
+ if (ptm_fd < 0) {
+ perror("posix_openpt");
+ ASSERT_NOT_REACHED();
+ }
+ if (grantpt(ptm_fd) < 0) {
+ perror("grantpt");
+ ASSERT_NOT_REACHED();
+ }
+ if (unlockpt(ptm_fd) < 0) {
+ perror("unlockpt");
+ ASSERT_NOT_REACHED();
+ }
+
+ m_terminal_widget->set_pty_master_fd(ptm_fd);
+ m_terminal_widget->on_command_exit = [this] {
+ int wstatus;
+ int rc = waitpid(m_pid, &wstatus, 0);
+ if (rc < 0) {
+ perror("waitpid");
+ ASSERT_NOT_REACHED();
+ }
+ if (WIFEXITED(wstatus)) {
+ m_terminal_widget->inject_string(String::formatted("\033[{};1m(Command exited with code {})\033[0m\n", wstatus == 0 ? 32 : 31, WEXITSTATUS(wstatus)));
+ } else if (WIFSTOPPED(wstatus)) {
+ m_terminal_widget->inject_string("\033[34;1m(Command stopped!)\033[0m\n");
+ } else if (WIFSIGNALED(wstatus)) {
+ m_terminal_widget->inject_string(String::formatted("\033[34;1m(Command signaled with {}!)\033[0m\n", strsignal(WTERMSIG(wstatus))));
+ }
+ m_pid = -1;
+
+ if (on_command_exit)
+ on_command_exit();
+ };
+
+ m_pid = fork();
+ if (m_pid == 0) {
+ // Create a new process group.
+ setsid();
+
+ const char* tty_name = ptsname(ptm_fd);
+ if (!tty_name) {
+ perror("ptsname");
+ exit(1);
+ }
+ close(ptm_fd);
+ int pts_fd = open(tty_name, O_RDWR);
+ if (pts_fd < 0) {
+ perror("open");
+ exit(1);
+ }
+
+ tcsetpgrp(pts_fd, getpid());
+
+ // NOTE: It's okay if this fails.
+ int rc = ioctl(0, TIOCNOTTY);
+
+ close(0);
+ close(1);
+ close(2);
+
+ rc = dup2(pts_fd, 0);
+ if (rc < 0) {
+ perror("dup2");
+ exit(1);
+ }
+ rc = dup2(pts_fd, 1);
+ if (rc < 0) {
+ perror("dup2");
+ exit(1);
+ }
+ rc = dup2(pts_fd, 2);
+ if (rc < 0) {
+ perror("dup2");
+ exit(1);
+ }
+ rc = close(pts_fd);
+ if (rc < 0) {
+ perror("close");
+ exit(1);
+ }
+ rc = ioctl(0, TIOCSCTTY);
+ if (rc < 0) {
+ perror("ioctl(TIOCSCTTY)");
+ exit(1);
+ }
+
+ setenv("TERM", "xterm", true);
+
+ auto parts = command.split(' ');
+ ASSERT(!parts.is_empty());
+ const char** args = (const char**)calloc(parts.size() + 1, sizeof(const char*));
+ for (size_t i = 0; i < parts.size(); i++) {
+ args[i] = parts[i].characters();
+ }
+ rc = execvp(args[0], const_cast<char**>(args));
+ if (rc < 0) {
+ perror("execve");
+ exit(1);
+ }
+ ASSERT_NOT_REACHED();
+ }
+
+ // (In parent process)
+ terminal().scroll_to_bottom();
+}
+
+void TerminalWrapper::kill_running_command()
+{
+ ASSERT(m_pid != -1);
+
+ // Kill our child process and its whole process group.
+ [[maybe_unused]] auto rc = killpg(m_pid, SIGTERM);
+}
+
+TerminalWrapper::TerminalWrapper(bool user_spawned)
+ : m_user_spawned(user_spawned)
+{
+ set_layout<GUI::VerticalBoxLayout>();
+
+ RefPtr<Core::ConfigFile> config = Core::ConfigFile::get_for_app("Terminal");
+ m_terminal_widget = add<TerminalWidget>(-1, false, config);
+
+ if (user_spawned)
+ run_command("Shell");
+}
+
+TerminalWrapper::~TerminalWrapper()
+{
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/TerminalWrapper.h b/Userland/DevTools/HackStudio/TerminalWrapper.h
new file mode 100644
index 0000000000..9b5a8ed0a3
--- /dev/null
+++ b/Userland/DevTools/HackStudio/TerminalWrapper.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+class TerminalWidget;
+
+namespace HackStudio {
+
+class TerminalWrapper final : public GUI::Widget {
+ C_OBJECT(TerminalWrapper)
+public:
+ virtual ~TerminalWrapper() override;
+
+ void run_command(const String&);
+ void kill_running_command();
+
+ bool user_spawned() const { return m_user_spawned; }
+ TerminalWidget& terminal() { return *m_terminal_widget; }
+
+ Function<void()> on_command_exit;
+
+private:
+ explicit TerminalWrapper(bool user_spawned = true);
+
+ RefPtr<TerminalWidget> m_terminal_widget;
+ pid_t m_pid { -1 };
+ bool m_user_spawned { true };
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/Tool.h b/Userland/DevTools/HackStudio/Tool.h
new file mode 100644
index 0000000000..63c9960a9d
--- /dev/null
+++ b/Userland/DevTools/HackStudio/Tool.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Noncopyable.h>
+#include <LibGUI/Forward.h>
+
+namespace HackStudio {
+
+class FormEditorWidget;
+
+class Tool {
+ AK_MAKE_NONCOPYABLE(Tool);
+ AK_MAKE_NONMOVABLE(Tool);
+
+public:
+ virtual ~Tool() { }
+
+ virtual void on_mousedown(GUI::MouseEvent&) = 0;
+ virtual void on_mouseup(GUI::MouseEvent&) = 0;
+ virtual void on_mousemove(GUI::MouseEvent&) = 0;
+ virtual void on_keydown(GUI::KeyEvent&) = 0;
+ virtual void on_second_paint(GUI::Painter&, GUI::PaintEvent&) { }
+
+ virtual const char* class_name() const = 0;
+
+ virtual void attach() { }
+ virtual void detach() { }
+
+protected:
+ explicit Tool(FormEditorWidget& editor)
+ : m_editor(editor)
+ {
+ }
+
+ FormEditorWidget& m_editor;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/WidgetTool.cpp b/Userland/DevTools/HackStudio/WidgetTool.cpp
new file mode 100644
index 0000000000..73626ebbf8
--- /dev/null
+++ b/Userland/DevTools/HackStudio/WidgetTool.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "WidgetTool.h"
+#include <AK/LogStream.h>
+
+namespace HackStudio {
+
+void WidgetTool::on_mousedown([[maybe_unused]] GUI::MouseEvent& event)
+{
+ dbgln("WidgetTool::on_mousedown");
+}
+
+void WidgetTool::on_mouseup([[maybe_unused]] GUI::MouseEvent& event)
+{
+ dbgln("WidgetTool::on_mouseup");
+}
+
+void WidgetTool::on_mousemove([[maybe_unused]] GUI::MouseEvent& event)
+{
+ dbgln("WidgetTool::on_mousemove");
+}
+
+void WidgetTool::on_keydown([[maybe_unused]] GUI::KeyEvent& event)
+{
+ dbgln("WidgetTool::on_keydown");
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/WidgetTool.h b/Userland/DevTools/HackStudio/WidgetTool.h
new file mode 100644
index 0000000000..dae5e805d3
--- /dev/null
+++ b/Userland/DevTools/HackStudio/WidgetTool.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Tool.h"
+
+namespace HackStudio {
+
+class WidgetTool final : public Tool {
+public:
+ explicit WidgetTool(FormEditorWidget& editor, const GUI::WidgetClassRegistration& meta_class)
+ : Tool(editor)
+ , m_meta_class(meta_class)
+ {
+ }
+ virtual ~WidgetTool() override { }
+
+private:
+ virtual const char* class_name() const override { return "WidgetTool"; }
+ virtual void on_mousedown(GUI::MouseEvent&) override;
+ virtual void on_mouseup(GUI::MouseEvent&) override;
+ virtual void on_mousemove(GUI::MouseEvent&) override;
+ virtual void on_keydown(GUI::KeyEvent&) override;
+
+ const GUI::WidgetClassRegistration& m_meta_class;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/WidgetTreeModel.cpp b/Userland/DevTools/HackStudio/WidgetTreeModel.cpp
new file mode 100644
index 0000000000..c0f8b403dc
--- /dev/null
+++ b/Userland/DevTools/HackStudio/WidgetTreeModel.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "WidgetTreeModel.h"
+#include <AK/StringBuilder.h>
+#include <LibGUI/Widget.h>
+
+namespace HackStudio {
+
+WidgetTreeModel::WidgetTreeModel(GUI::Widget& root)
+ : m_root(root)
+{
+ m_widget_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png"));
+}
+
+WidgetTreeModel::~WidgetTreeModel()
+{
+}
+
+GUI::ModelIndex WidgetTreeModel::index(int row, int column, const GUI::ModelIndex& parent) const
+{
+ if (!parent.is_valid()) {
+ return create_index(row, column, m_root.ptr());
+ }
+ auto& parent_node = *static_cast<GUI::Widget*>(parent.internal_data());
+ return create_index(row, column, parent_node.child_widgets().at(row));
+}
+
+GUI::ModelIndex WidgetTreeModel::parent_index(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+ auto& widget = *static_cast<GUI::Widget*>(index.internal_data());
+ if (&widget == m_root.ptr())
+ return {};
+
+ if (widget.parent_widget() == m_root.ptr())
+ return create_index(0, 0, m_root.ptr());
+
+ // Walk the grandparent's children to find the index of widget's parent in its parent.
+ // (This is needed to produce the row number of the GUI::ModelIndex corresponding to widget's parent.)
+ int grandparent_child_index = 0;
+ for (auto& grandparent_child : widget.parent_widget()->parent_widget()->child_widgets()) {
+ if (grandparent_child == widget.parent_widget())
+ return create_index(grandparent_child_index, 0, widget.parent_widget());
+ ++grandparent_child_index;
+ }
+
+ ASSERT_NOT_REACHED();
+ return {};
+}
+
+int WidgetTreeModel::row_count(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return 1;
+ auto& widget = *static_cast<GUI::Widget*>(index.internal_data());
+ return widget.child_widgets().size();
+}
+
+int WidgetTreeModel::column_count(const GUI::ModelIndex&) const
+{
+ return 1;
+}
+
+GUI::Variant WidgetTreeModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto* widget = static_cast<GUI::Widget*>(index.internal_data());
+ if (role == GUI::ModelRole::Icon) {
+ return m_widget_icon;
+ }
+ if (role == GUI::ModelRole::Display) {
+ return String::formatted("{} ({})", widget->class_name(), widget->relative_rect());
+ }
+ return {};
+}
+
+void WidgetTreeModel::update()
+{
+ did_update();
+}
+
+GUI::ModelIndex WidgetTreeModel::index_for_widget(GUI::Widget& widget) const
+{
+ int parent_child_index = 0;
+ for (auto& parent_child : widget.parent_widget()->child_widgets()) {
+ if (parent_child == &widget)
+ return create_index(parent_child_index, 0, &widget);
+ ++parent_child_index;
+ }
+ return {};
+}
+
+}
diff --git a/Userland/DevTools/HackStudio/WidgetTreeModel.h b/Userland/DevTools/HackStudio/WidgetTreeModel.h
new file mode 100644
index 0000000000..cfffbd6add
--- /dev/null
+++ b/Userland/DevTools/HackStudio/WidgetTreeModel.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Model.h>
+#include <LibGUI/Painter.h>
+
+namespace HackStudio {
+
+class WidgetTreeModel final : public GUI::Model {
+public:
+ static NonnullRefPtr<WidgetTreeModel> create(GUI::Widget& root) { return adopt(*new WidgetTreeModel(root)); }
+ virtual ~WidgetTreeModel() override;
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+ virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override;
+ virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override;
+ virtual void update() override;
+
+ GUI::ModelIndex index_for_widget(GUI::Widget&) const;
+
+private:
+ explicit WidgetTreeModel(GUI::Widget&);
+
+ NonnullRefPtr<GUI::Widget> m_root;
+ GUI::Icon m_widget_icon;
+};
+
+}
diff --git a/Userland/DevTools/HackStudio/main.cpp b/Userland/DevTools/HackStudio/main.cpp
new file mode 100644
index 0000000000..3ca8b1a8a0
--- /dev/null
+++ b/Userland/DevTools/HackStudio/main.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "HackStudio.h"
+#include "HackStudioWidget.h"
+#include "Project.h"
+#include <AK/StringBuilder.h>
+#include <LibCore/ArgsParser.h>
+#include <LibCore/Event.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/File.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/MenuBar.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGUI/Widget.h>
+#include <LibGUI/Window.h>
+#include <LibThread/Lock.h>
+#include <LibThread/Thread.h>
+#include <LibVT/TerminalWidget.h>
+#include <fcntl.h>
+#include <spawn.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+using namespace HackStudio;
+
+static RefPtr<GUI::Window> s_window;
+static RefPtr<HackStudioWidget> s_hack_studio_widget;
+
+static bool make_is_available();
+static void update_path_environment_variable();
+
+int main(int argc, char** argv)
+{
+ if (pledge("stdio tty accept rpath cpath wpath shared_buffer proc exec unix fattr thread unix sendfd ptrace", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ auto app = GUI::Application::construct(argc, argv);
+
+ if (pledge("stdio tty accept rpath cpath wpath shared_buffer proc exec fattr thread unix sendfd ptrace", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ s_window = GUI::Window::construct();
+ s_window->resize(840, 600);
+ s_window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-hack-studio.png"));
+
+ update_path_environment_variable();
+
+ if (!make_is_available())
+ GUI::MessageBox::show(s_window, "The 'make' command is not available. You probably want to install the binutils, gcc, and make ports from the root of the Serenity repository.", "Error", GUI::MessageBox::Type::Error);
+
+ const char* path_argument = nullptr;
+ Core::ArgsParser args_parser;
+ args_parser.add_positional_argument(path_argument, "Path to a workspace or a file", "path", Core::ArgsParser::Required::No);
+ args_parser.parse(argc, argv);
+
+ auto argument_absolute_path = Core::File::real_path_for(path_argument);
+
+ auto project_path = argument_absolute_path;
+ if (argument_absolute_path.is_null())
+ project_path = Core::File::real_path_for(".");
+
+ s_hack_studio_widget = s_window->set_main_widget<HackStudioWidget>(project_path);
+
+ s_window->set_title(String::formatted("{} - Hack Studio", s_hack_studio_widget->project().name()));
+
+ auto menubar = GUI::MenuBar::construct();
+ s_hack_studio_widget->initialize_menubar(menubar);
+ app->set_menubar(menubar);
+
+ s_window->show();
+
+ s_hack_studio_widget->update_actions();
+
+ return app->exec();
+}
+
+static bool make_is_available()
+{
+ pid_t pid;
+ const char* argv[] = { "make", "--version", nullptr };
+ posix_spawn_file_actions_t action;
+ posix_spawn_file_actions_init(&action);
+ posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
+ if ((errno = posix_spawnp(&pid, "make", &action, nullptr, const_cast<char**>(argv), environ))) {
+ perror("posix_spawn");
+ return false;
+ }
+ int wstatus;
+ waitpid(pid, &wstatus, 0);
+ posix_spawn_file_actions_destroy(&action);
+ return WEXITSTATUS(wstatus) == 0;
+}
+
+static void update_path_environment_variable()
+{
+ StringBuilder path;
+ path.append(getenv("PATH"));
+ if (path.length())
+ path.append(":");
+ path.append("/bin:/usr/bin:/usr/local/bin");
+ setenv("PATH", path.to_string().characters(), true);
+}
+
+namespace HackStudio {
+
+GUI::TextEditor& current_editor()
+{
+ return s_hack_studio_widget->current_editor();
+}
+
+void open_file(const String& file_name)
+{
+ return s_hack_studio_widget->open_file(file_name);
+}
+
+RefPtr<EditorWrapper> current_editor_wrapper()
+{
+ if (!s_hack_studio_widget)
+ return nullptr;
+ return s_hack_studio_widget->current_editor_wrapper();
+}
+
+Project& project()
+{
+ return s_hack_studio_widget->project();
+}
+
+String currently_open_file()
+{
+ if (!s_hack_studio_widget)
+ return {};
+ return s_hack_studio_widget->currently_open_file();
+}
+
+void set_current_editor_wrapper(RefPtr<EditorWrapper> wrapper)
+{
+ s_hack_studio_widget->set_current_editor_wrapper(wrapper);
+}
+
+}
diff --git a/Userland/DevTools/IPCCompiler/CMakeLists.txt b/Userland/DevTools/IPCCompiler/CMakeLists.txt
new file mode 100644
index 0000000000..6969cef777
--- /dev/null
+++ b/Userland/DevTools/IPCCompiler/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES
+ main.cpp
+)
+
+add_executable(IPCCompiler ${SOURCES})
+target_link_libraries(IPCCompiler LagomCore)
diff --git a/Userland/DevTools/IPCCompiler/main.cpp b/Userland/DevTools/IPCCompiler/main.cpp
new file mode 100644
index 0000000000..6b3ef51a50
--- /dev/null
+++ b/Userland/DevTools/IPCCompiler/main.cpp
@@ -0,0 +1,613 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Function.h>
+#include <AK/GenericLexer.h>
+#include <AK/HashMap.h>
+#include <AK/SourceGenerator.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/File.h>
+#include <ctype.h>
+#include <stdio.h>
+
+//#define GENERATE_DEBUG_CODE
+
+struct Parameter {
+ Vector<String> attributes;
+ String type;
+ String name;
+};
+
+struct Message {
+ String name;
+ bool is_synchronous { false };
+ Vector<Parameter> inputs;
+ Vector<Parameter> outputs;
+
+ String response_name() const
+ {
+ StringBuilder builder;
+ builder.append(name);
+ builder.append("Response");
+ return builder.to_string();
+ }
+};
+
+struct Endpoint {
+ String name;
+ int magic;
+ Vector<Message> messages;
+};
+
+int main(int argc, char** argv)
+{
+ if (argc != 2) {
+ outln("usage: {} <IPC endpoint definition file>", argv[0]);
+ return 0;
+ }
+
+ auto file = Core::File::construct(argv[1]);
+ if (!file->open(Core::IODevice::ReadOnly)) {
+ warnln("Error: Cannot open {}: {}", argv[1], file->error_string());
+ return 1;
+ }
+
+ auto file_contents = file->read_all();
+ GenericLexer lexer(file_contents);
+
+ Vector<Endpoint> endpoints;
+
+ auto assert_specific = [&](char ch) {
+ if (lexer.peek() != ch)
+ warnln("assert_specific: wanted '{}', but got '{}' at index {}", ch, lexer.peek(), lexer.tell());
+ bool saw_expected = lexer.consume_specific(ch);
+ ASSERT(saw_expected);
+ };
+
+ auto consume_whitespace = [&] {
+ lexer.ignore_while([](char ch) { return isspace(ch); });
+ if (lexer.peek() == '/' && lexer.peek(1) == '/')
+ lexer.ignore_until([](char ch) { return ch == '\n'; });
+ };
+
+ auto parse_parameter = [&](Vector<Parameter>& storage) {
+ for (;;) {
+ Parameter parameter;
+ consume_whitespace();
+ if (lexer.peek() == ')')
+ break;
+ if (lexer.consume_specific('[')) {
+ for (;;) {
+ if (lexer.consume_specific(']')) {
+ consume_whitespace();
+ break;
+ }
+ if (lexer.consume_specific(',')) {
+ consume_whitespace();
+ }
+ auto attribute = lexer.consume_until([](char ch) { return ch == ']' || ch == ','; });
+ parameter.attributes.append(attribute);
+ consume_whitespace();
+ }
+ }
+ parameter.type = lexer.consume_until([](char ch) { return isspace(ch); });
+ consume_whitespace();
+ parameter.name = lexer.consume_until([](char ch) { return isspace(ch) || ch == ',' || ch == ')'; });
+ consume_whitespace();
+ storage.append(move(parameter));
+ if (lexer.consume_specific(','))
+ continue;
+ if (lexer.peek() == ')')
+ break;
+ }
+ };
+
+ auto parse_parameters = [&](Vector<Parameter>& storage) {
+ for (;;) {
+ consume_whitespace();
+ parse_parameter(storage);
+ consume_whitespace();
+ if (lexer.consume_specific(','))
+ continue;
+ if (lexer.peek() == ')')
+ break;
+ }
+ };
+
+ auto parse_message = [&] {
+ Message message;
+ consume_whitespace();
+ message.name = lexer.consume_until([](char ch) { return isspace(ch) || ch == '('; });
+ consume_whitespace();
+ assert_specific('(');
+ parse_parameters(message.inputs);
+ assert_specific(')');
+ consume_whitespace();
+ assert_specific('=');
+
+ auto type = lexer.consume();
+ if (type == '>')
+ message.is_synchronous = true;
+ else if (type == '|')
+ message.is_synchronous = false;
+ else
+ ASSERT_NOT_REACHED();
+
+ consume_whitespace();
+
+ if (message.is_synchronous) {
+ assert_specific('(');
+ parse_parameters(message.outputs);
+ assert_specific(')');
+ }
+
+ consume_whitespace();
+
+ endpoints.last().messages.append(move(message));
+ };
+
+ auto parse_messages = [&] {
+ for (;;) {
+ consume_whitespace();
+ parse_message();
+ consume_whitespace();
+ if (lexer.peek() == '}')
+ break;
+ }
+ };
+
+ auto parse_endpoint = [&] {
+ endpoints.empend();
+ consume_whitespace();
+ lexer.consume_specific("endpoint");
+ consume_whitespace();
+ endpoints.last().name = lexer.consume_while([](char ch) { return !isspace(ch); });
+ consume_whitespace();
+ assert_specific('=');
+ consume_whitespace();
+ auto magic_string = lexer.consume_while([](char ch) { return !isspace(ch) && ch != '{'; });
+ endpoints.last().magic = magic_string.to_int().value();
+ consume_whitespace();
+ assert_specific('{');
+ parse_messages();
+ assert_specific('}');
+ consume_whitespace();
+ };
+
+ while (lexer.tell() < file_contents.size())
+ parse_endpoint();
+
+ StringBuilder builder;
+ SourceGenerator generator { builder };
+
+ generator.append(R"~~~(
+#pragma once
+#include <AK/MemoryStream.h>
+#include <AK/OwnPtr.h>
+#include <AK/URL.h>
+#include <AK/Utf8View.h>
+#include <LibGfx/Color.h>
+#include <LibGfx/Rect.h>
+#include <LibGfx/ShareableBitmap.h>
+#include <LibIPC/Decoder.h>
+#include <LibIPC/Dictionary.h>
+#include <LibIPC/Encoder.h>
+#include <LibIPC/Endpoint.h>
+#include <LibIPC/File.h>
+#include <LibIPC/Message.h>
+)~~~");
+
+ for (auto& endpoint : endpoints) {
+ auto endpoint_generator = generator.fork();
+
+ endpoint_generator.set("endpoint.name", endpoint.name);
+ endpoint_generator.set("endpoint.magic", String::number(endpoint.magic));
+
+ endpoint_generator.append(R"~~~(
+namespace Messages::@endpoint.name@ {
+)~~~");
+
+ HashMap<String, int> message_ids;
+
+ endpoint_generator.append(R"~~~(
+enum class MessageID : i32 {
+)~~~");
+ for (auto& message : endpoint.messages) {
+ auto message_generator = endpoint_generator.fork();
+
+ message_ids.set(message.name, message_ids.size() + 1);
+ message_generator.set("message.name", message.name);
+ message_generator.set("message.id", String::number(message_ids.size()));
+
+ message_generator.append(R"~~~(
+ @message.name@ = @message.id@,
+)~~~");
+ if (message.is_synchronous) {
+ message_ids.set(message.response_name(), message_ids.size() + 1);
+ message_generator.set("message.name", message.response_name());
+ message_generator.set("message.id", String::number(message_ids.size()));
+
+ message_generator.append(R"~~~(
+ @message.name@ = @message.id@,
+)~~~");
+ }
+ }
+ endpoint_generator.append(R"~~~(
+};
+)~~~");
+
+ auto constructor_for_message = [&](const String& name, const Vector<Parameter>& parameters) {
+ StringBuilder builder;
+ builder.append(name);
+
+ if (parameters.is_empty()) {
+ builder.append("() {}");
+ return builder.to_string();
+ }
+
+ builder.append('(');
+ for (size_t i = 0; i < parameters.size(); ++i) {
+ auto& parameter = parameters[i];
+ builder.append("const ");
+ builder.append(parameter.type);
+ builder.append("& ");
+ builder.append(parameter.name);
+ if (i != parameters.size() - 1)
+ builder.append(", ");
+ }
+ builder.append(") : ");
+ for (size_t i = 0; i < parameters.size(); ++i) {
+ auto& parameter = parameters[i];
+ builder.append("m_");
+ builder.append(parameter.name);
+ builder.append("(");
+ builder.append(parameter.name);
+ builder.append(")");
+ if (i != parameters.size() - 1)
+ builder.append(", ");
+ }
+ builder.append(" {}");
+ return builder.to_string();
+ };
+
+ auto do_message = [&](const String& name, const Vector<Parameter>& parameters, const String& response_type = {}) {
+ auto message_generator = endpoint_generator.fork();
+ message_generator.set("message.name", name);
+ message_generator.set("message.response_type", response_type);
+ message_generator.set("message.constructor", constructor_for_message(name, parameters));
+
+ message_generator.append(R"~~~(
+class @message.name@ final : public IPC::Message {
+public:
+)~~~");
+
+ if (!response_type.is_null())
+ message_generator.append(R"~~~(
+ typedef class @message.response_type@ ResponseType;
+)~~~");
+
+ message_generator.append(R"~~~(
+ @message.constructor@
+ virtual ~@message.name@() override {}
+
+ virtual i32 endpoint_magic() const override { return @endpoint.magic@; }
+ virtual i32 message_id() const override { return (int)MessageID::@message.name@; }
+ static i32 static_message_id() { return (int)MessageID::@message.name@; }
+ virtual const char* message_name() const override { return "@endpoint.name@::@message.name@"; }
+
+ static OwnPtr<@message.name@> decode(InputMemoryStream& stream, int sockfd)
+ {
+ IPC::Decoder decoder { stream, sockfd };
+)~~~");
+
+ for (auto& parameter : parameters) {
+ auto parameter_generator = message_generator.fork();
+
+ parameter_generator.set("parameter.type", parameter.type);
+ parameter_generator.set("parameter.name", parameter.name);
+
+ if (parameter.type == "bool")
+ parameter_generator.set("parameter.initial_value", "false");
+ else
+ parameter_generator.set("parameter.initial_value", "{}");
+
+ parameter_generator.append(R"~~~(
+ @parameter.type@ @parameter.name@ = @parameter.initial_value@;
+ if (!decoder.decode(@parameter.name@))
+ return {};
+)~~~");
+
+ if (parameter.attributes.contains_slow("UTF8")) {
+ parameter_generator.append(R"~~~(
+ if (!Utf8View(@parameter.name@).validate())
+ return {};
+)~~~");
+ }
+ }
+
+ StringBuilder builder;
+ for (size_t i = 0; i < parameters.size(); ++i) {
+ auto& parameter = parameters[i];
+ builder.append(parameter.name);
+ if (i != parameters.size() - 1)
+ builder.append(", ");
+ }
+
+ message_generator.set("message.constructor_call_parameters", builder.build());
+
+ message_generator.append(R"~~~(
+ return make<@message.name@>(@message.constructor_call_parameters@);
+ }
+)~~~");
+
+ message_generator.append(R"~~~(
+ virtual IPC::MessageBuffer encode() const override
+ {
+ IPC::MessageBuffer buffer;
+ IPC::Encoder stream(buffer);
+ stream << endpoint_magic();
+ stream << (int)MessageID::@message.name@;
+)~~~");
+
+ for (auto& parameter : parameters) {
+ auto parameter_generator = message_generator.fork();
+
+ parameter_generator.set("parameter.name", parameter.name);
+ parameter_generator.append(R"~~~(
+ stream << m_@parameter.name@;
+)~~~");
+ }
+
+ message_generator.append(R"~~~(
+ return buffer;
+ }
+)~~~");
+
+ for (auto& parameter : parameters) {
+ auto parameter_generator = message_generator.fork();
+ parameter_generator.set("parameter.type", parameter.type);
+ parameter_generator.set("parameter.name", parameter.name);
+ parameter_generator.append(R"~~~(
+ const @parameter.type@& @parameter.name@() const { return m_@parameter.name@; }
+)~~~");
+ }
+
+ message_generator.append(R"~~~(
+private:
+ )~~~");
+
+ for (auto& parameter : parameters) {
+ auto parameter_generator = message_generator.fork();
+ parameter_generator.set("parameter.type", parameter.type);
+ parameter_generator.set("parameter.name", parameter.name);
+ parameter_generator.append(R"~~~(
+ @parameter.type@ m_@parameter.name@;
+)~~~");
+ }
+
+ message_generator.append(R"~~~(
+};
+ )~~~");
+ };
+ for (auto& message : endpoint.messages) {
+ String response_name;
+ if (message.is_synchronous) {
+ response_name = message.response_name();
+ do_message(response_name, message.outputs);
+ }
+ do_message(message.name, message.inputs, response_name);
+ }
+
+ endpoint_generator.append(R"~~~(
+} // namespace Messages::@endpoint.name@
+ )~~~");
+
+ endpoint_generator.append(R"~~~(
+class @endpoint.name@Endpoint : public IPC::Endpoint {
+public:
+ @endpoint.name@Endpoint() { }
+ virtual ~@endpoint.name@Endpoint() override { }
+
+ static int static_magic() { return @endpoint.magic@; }
+ virtual int magic() const override { return @endpoint.magic@; }
+ static String static_name() { return "@endpoint.name@"; }
+ virtual String name() const override { return "@endpoint.name@"; }
+
+ static OwnPtr<IPC::Message> decode_message(ReadonlyBytes buffer, int sockfd)
+ {
+ InputMemoryStream stream { buffer };
+ i32 message_endpoint_magic = 0;
+ stream >> message_endpoint_magic;
+ if (stream.handle_any_error()) {
+)~~~");
+#ifdef GENERATE_DEBUG_CODE
+ endpoint_generator.append(R"~~~(
+ dbgln("Failed to read message endpoint magic");
+)~~~");
+#endif
+ endpoint_generator.append(R"~~~(
+ return {};
+ }
+
+ if (message_endpoint_magic != @endpoint.magic@) {
+)~~~");
+#ifdef GENERATE_DEBUG_CODE
+ endpoint_generator.append(R"~~~(
+ dbgln("Endpoint magic number message_endpoint_magic != @endpoint.magic@");
+)~~~");
+#endif
+ endpoint_generator.append(R"~~~(
+ return {};
+ }
+
+ i32 message_id = 0;
+ stream >> message_id;
+ if (stream.handle_any_error()) {
+)~~~");
+#ifdef GENERATE_DEBUG_CODE
+ endpoint_generator.append(R"~~~(
+ dbgln("Failed to read message ID");
+)~~~");
+#endif
+ endpoint_generator.append(R"~~~(
+ return {};
+ }
+
+ OwnPtr<IPC::Message> message;
+ switch (message_id) {
+)~~~");
+
+ for (auto& message : endpoint.messages) {
+ auto do_decode_message = [&](const String& name) {
+ auto message_generator = endpoint_generator.fork();
+
+ message_generator.set("message.name", name);
+
+ message_generator.append(R"~~~(
+ case (int)Messages::@endpoint.name@::MessageID::@message.name@:
+ message = Messages::@endpoint.name@::@message.name@::decode(stream, sockfd);
+ break;
+)~~~");
+ };
+
+ do_decode_message(message.name);
+ if (message.is_synchronous)
+ do_decode_message(message.response_name());
+ }
+
+ endpoint_generator.append(R"~~~(
+ default:
+)~~~");
+#ifdef GENERATE_DEBUG_CODE
+ endpoint_generator.append(R"~~~(
+ dbgln("Failed to decode @endpoint.name@.({})", message_id);
+)~~~");
+#endif
+ endpoint_generator.append(R"~~~(
+ return {};
+ }
+
+ if (stream.handle_any_error()) {
+)~~~");
+#ifdef GENERATE_DEBUG_CODE
+ endpoint_generator.append(R"~~~(
+ dbgln("Failed to read the message");
+)~~~");
+#endif
+ endpoint_generator.append(R"~~~(
+ return {};
+ }
+
+ return message;
+ }
+
+ virtual OwnPtr<IPC::Message> handle(const IPC::Message& message) override
+ {
+ switch (message.message_id()) {
+)~~~");
+ for (auto& message : endpoint.messages) {
+ auto do_decode_message = [&](const String& name, bool returns_something) {
+ auto message_generator = endpoint_generator.fork();
+
+ message_generator.set("message.name", name);
+ message_generator.append(R"~~~(
+ case (int)Messages::@endpoint.name@::MessageID::@message.name@:
+)~~~");
+ if (returns_something) {
+ message_generator.append(R"~~~(
+ return handle(static_cast<const Messages::@endpoint.name@::@message.name@&>(message));
+)~~~");
+ } else {
+ message_generator.append(R"~~~(
+ handle(static_cast<const Messages::@endpoint.name@::@message.name@&>(message));
+ return {};
+)~~~");
+ }
+ };
+ do_decode_message(message.name, message.is_synchronous);
+ if (message.is_synchronous)
+ do_decode_message(message.response_name(), false);
+ }
+ endpoint_generator.append(R"~~~(
+ default:
+ return {};
+ }
+ }
+)~~~");
+
+ for (auto& message : endpoint.messages) {
+ auto message_generator = endpoint_generator.fork();
+
+ message_generator.set("message.name", message.name);
+
+ String return_type = "void";
+ if (message.is_synchronous) {
+ StringBuilder builder;
+ builder.append("OwnPtr<Messages::");
+ builder.append(endpoint.name);
+ builder.append("::");
+ builder.append(message.name);
+ builder.append("Response");
+ builder.append(">");
+ return_type = builder.to_string();
+ }
+ message_generator.set("message.complex_return_type", return_type);
+
+ message_generator.append(R"~~~(
+ virtual @message.complex_return_type@ handle(const Messages::@endpoint.name@::@message.name@&) = 0;
+)~~~");
+ }
+
+ endpoint_generator.append(R"~~~(
+private:
+};
+)~~~");
+ }
+
+ outln("{}", generator.as_string_view());
+
+#ifdef DEBUG
+ for (auto& endpoint : endpoints) {
+ warnln("Endpoint '{}' (magic: {})", endpoint.name, endpoint.magic);
+ for (auto& message : endpoint.messages) {
+ warnln(" Message: '{}'", message.name);
+ warnln(" Sync: {}", message.is_synchronous);
+ warnln(" Inputs:");
+ for (auto& parameter : message.inputs)
+ warnln(" Parameter: {} ({})", parameter.name, parameter.type);
+ if (message.inputs.is_empty())
+ warnln(" (none)");
+ if (message.is_synchronous) {
+ warnln(" Outputs:");
+ for (auto& parameter : message.outputs)
+ warnln(" Parameter: {} ({})", parameter.name, parameter.type);
+ if (message.outputs.is_empty())
+ warnln(" (none)");
+ }
+ }
+ }
+#endif
+}
diff --git a/Userland/DevTools/Inspector/CMakeLists.txt b/Userland/DevTools/Inspector/CMakeLists.txt
new file mode 100644
index 0000000000..dd980cec99
--- /dev/null
+++ b/Userland/DevTools/Inspector/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES
+ main.cpp
+ RemoteObject.cpp
+ RemoteObjectGraphModel.cpp
+ RemoteObjectPropertyModel.cpp
+ RemoteProcess.cpp
+)
+
+serenity_app(Inspector ICON app-inspector)
+target_link_libraries(Inspector LibGUI)
diff --git a/Userland/DevTools/Inspector/RemoteObject.cpp b/Userland/DevTools/Inspector/RemoteObject.cpp
new file mode 100644
index 0000000000..ce88259482
--- /dev/null
+++ b/Userland/DevTools/Inspector/RemoteObject.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RemoteObject.h"
+#include "RemoteObjectPropertyModel.h"
+
+namespace Inspector {
+
+RemoteObject::RemoteObject()
+ : m_property_model(RemoteObjectPropertyModel::create(*this))
+{
+}
+
+RemoteObjectPropertyModel& RemoteObject::property_model()
+{
+ m_property_model->update();
+ return *m_property_model;
+}
+
+}
diff --git a/Userland/DevTools/Inspector/RemoteObject.h b/Userland/DevTools/Inspector/RemoteObject.h
new file mode 100644
index 0000000000..1c231f2f01
--- /dev/null
+++ b/Userland/DevTools/Inspector/RemoteObject.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/JsonObject.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/String.h>
+#include <AK/Vector.h>
+
+namespace Inspector {
+
+class RemoteObjectPropertyModel;
+
+class RemoteObject {
+public:
+ RemoteObject();
+
+ RemoteObjectPropertyModel& property_model();
+
+ RemoteObject* parent { nullptr };
+ NonnullOwnPtrVector<RemoteObject> children;
+
+ FlatPtr address { 0 };
+ FlatPtr parent_address { 0 };
+ String class_name;
+ String name;
+
+ JsonObject json;
+
+ NonnullRefPtr<RemoteObjectPropertyModel> m_property_model;
+};
+
+}
diff --git a/Userland/DevTools/Inspector/RemoteObjectGraphModel.cpp b/Userland/DevTools/Inspector/RemoteObjectGraphModel.cpp
new file mode 100644
index 0000000000..32caac30ec
--- /dev/null
+++ b/Userland/DevTools/Inspector/RemoteObjectGraphModel.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RemoteObjectGraphModel.h"
+#include "RemoteObject.h"
+#include "RemoteProcess.h"
+#include <AK/JsonObject.h>
+#include <AK/JsonValue.h>
+#include <LibGUI/Application.h>
+#include <stdio.h>
+
+namespace Inspector {
+
+RemoteObjectGraphModel::RemoteObjectGraphModel(RemoteProcess& process)
+ : m_process(process)
+{
+ m_object_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png"));
+ m_window_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/window.png"));
+ m_layout_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/layout.png"));
+ m_timer_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/timer.png"));
+}
+
+RemoteObjectGraphModel::~RemoteObjectGraphModel()
+{
+}
+
+GUI::ModelIndex RemoteObjectGraphModel::index(int row, int column, const GUI::ModelIndex& parent) const
+{
+ if (!parent.is_valid()) {
+ if (m_process.roots().is_empty())
+ return {};
+ return create_index(row, column, &m_process.roots().at(row));
+ }
+ auto& remote_parent = *static_cast<RemoteObject*>(parent.internal_data());
+ return create_index(row, column, &remote_parent.children.at(row));
+}
+
+GUI::ModelIndex RemoteObjectGraphModel::parent_index(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+ auto& remote_object = *static_cast<RemoteObject*>(index.internal_data());
+ if (!remote_object.parent)
+ return {};
+
+ // NOTE: If the parent has no parent, it's a root, so we have to look among the remote roots.
+ if (!remote_object.parent->parent) {
+ for (size_t row = 0; row < m_process.roots().size(); ++row) {
+ if (&m_process.roots()[row] == remote_object.parent)
+ return create_index(row, 0, remote_object.parent);
+ }
+ ASSERT_NOT_REACHED();
+ return {};
+ }
+
+ for (size_t row = 0; row < remote_object.parent->parent->children.size(); ++row) {
+ if (&remote_object.parent->parent->children[row] == remote_object.parent)
+ return create_index(row, 0, remote_object.parent);
+ }
+
+ ASSERT_NOT_REACHED();
+ return {};
+}
+
+int RemoteObjectGraphModel::row_count(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return m_process.roots().size();
+ auto& remote_object = *static_cast<RemoteObject*>(index.internal_data());
+ return remote_object.children.size();
+}
+
+int RemoteObjectGraphModel::column_count(const GUI::ModelIndex&) const
+{
+ return 1;
+}
+
+GUI::Variant RemoteObjectGraphModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto* remote_object = static_cast<RemoteObject*>(index.internal_data());
+ if (role == GUI::ModelRole::Icon) {
+ if (remote_object->class_name == "Window")
+ return m_window_icon;
+ if (remote_object->class_name == "Timer")
+ return m_timer_icon;
+ if (remote_object->class_name.ends_with("Layout"))
+ return m_layout_icon;
+ return m_object_icon;
+ }
+ if (role == GUI::ModelRole::Display)
+ return String::formatted("{}({:p})", remote_object->class_name, remote_object->address);
+
+ return {};
+}
+
+void RemoteObjectGraphModel::update()
+{
+ did_update();
+}
+
+}
diff --git a/Userland/DevTools/Inspector/RemoteObjectGraphModel.h b/Userland/DevTools/Inspector/RemoteObjectGraphModel.h
new file mode 100644
index 0000000000..efcdab3cf4
--- /dev/null
+++ b/Userland/DevTools/Inspector/RemoteObjectGraphModel.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/JsonArray.h>
+#include <AK/JsonObject.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <LibCore/LocalSocket.h>
+#include <LibGUI/Model.h>
+
+namespace Inspector {
+
+class RemoteProcess;
+
+class RemoteObjectGraphModel final : public GUI::Model {
+public:
+ static NonnullRefPtr<RemoteObjectGraphModel> create(RemoteProcess& process)
+ {
+ return adopt(*new RemoteObjectGraphModel(process));
+ }
+
+ virtual ~RemoteObjectGraphModel() override;
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+ virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override;
+ virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override;
+ virtual void update() override;
+
+private:
+ explicit RemoteObjectGraphModel(RemoteProcess&);
+
+ RemoteProcess& m_process;
+
+ GUI::Icon m_object_icon;
+ GUI::Icon m_window_icon;
+ GUI::Icon m_layout_icon;
+ GUI::Icon m_timer_icon;
+};
+
+}
diff --git a/Userland/DevTools/Inspector/RemoteObjectPropertyModel.cpp b/Userland/DevTools/Inspector/RemoteObjectPropertyModel.cpp
new file mode 100644
index 0000000000..6c9e82f3a7
--- /dev/null
+++ b/Userland/DevTools/Inspector/RemoteObjectPropertyModel.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RemoteObjectPropertyModel.h"
+#include "RemoteObject.h"
+#include "RemoteProcess.h"
+
+namespace Inspector {
+
+RemoteObjectPropertyModel::RemoteObjectPropertyModel(RemoteObject& object)
+ : m_object(object)
+{
+}
+
+int RemoteObjectPropertyModel::row_count(const GUI::ModelIndex& index) const
+{
+ Function<int(const JsonValue&)> do_count = [&](const JsonValue& value) {
+ if (value.is_array())
+ return value.as_array().size();
+ else if (value.is_object())
+ return value.as_object().size();
+ return 0;
+ };
+
+ if (index.is_valid()) {
+ auto* path = static_cast<const JsonPath*>(index.internal_data());
+ return do_count(path->resolve(m_object.json));
+ } else {
+ return do_count(m_object.json);
+ }
+}
+
+String RemoteObjectPropertyModel::column_name(int column) const
+{
+ switch (column) {
+ case Column::Name:
+ return "Name";
+ case Column::Value:
+ return "Value";
+ }
+ ASSERT_NOT_REACHED();
+}
+
+GUI::Variant RemoteObjectPropertyModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto* path = static_cast<const JsonPath*>(index.internal_data());
+ if (!path)
+ return {};
+
+ if (role == GUI::ModelRole::Display) {
+ switch (index.column()) {
+ case Column::Name:
+ return path->last().to_string();
+ case Column::Value: {
+ auto data = path->resolve(m_object.json);
+ if (data.is_array())
+ return String::formatted("<Array with {} element{}", data.as_array().size(), data.as_array().size() == 1 ? ">" : "s>");
+ if (data.is_object())
+ return String::formatted("<Object with {} entr{}", data.as_object().size(), data.as_object().size() == 1 ? "y>" : "ies>");
+ return data;
+ }
+ }
+ }
+ return {};
+}
+
+void RemoteObjectPropertyModel::update()
+{
+ did_update();
+}
+
+void RemoteObjectPropertyModel::set_data(const GUI::ModelIndex& index, const GUI::Variant& new_value)
+{
+ if (!index.is_valid())
+ return;
+
+ auto* path = static_cast<const JsonPath*>(index.internal_data());
+ if (path->size() != 1)
+ return;
+
+ FlatPtr address = m_object.address;
+ RemoteProcess::the().set_property(address, path->first().to_string(), new_value.to_string());
+ did_update();
+}
+
+GUI::ModelIndex RemoteObjectPropertyModel::index(int row, int column, const GUI::ModelIndex& parent) const
+{
+ const auto& parent_path = parent.is_valid() ? *static_cast<const JsonPath*>(parent.internal_data()) : JsonPath {};
+
+ auto nth_child = [&](int n, const JsonValue& value) -> const JsonPath* {
+ auto path = make<JsonPath>();
+ path->append(parent_path);
+ int row_index = n;
+ if (value.is_object()) {
+ String property_name;
+ auto& object = value.as_object();
+ object.for_each_member([&](auto& name, auto&) {
+ if (row_index > 0) {
+ --row_index;
+ } else if (row_index == 0) {
+ property_name = name;
+ --row_index;
+ }
+ });
+ if (property_name.is_null())
+ return nullptr;
+
+ path->append({ property_name });
+ m_paths.append(move(path));
+ } else if (value.is_array()) {
+ path->append(JsonPathElement { (size_t)n });
+ m_paths.append(move(path));
+ } else {
+ return nullptr;
+ }
+ return &m_paths.last();
+ };
+
+ if (!parent.is_valid()) {
+ if (m_object.json.is_empty())
+ return {};
+ }
+
+ auto index_path = cached_path_at(row, parent_path);
+
+ if (!index_path)
+ index_path = nth_child(row, parent_path.resolve(m_object.json));
+
+ if (!index_path)
+ return {};
+
+ return create_index(row, column, index_path);
+}
+
+GUI::ModelIndex RemoteObjectPropertyModel::parent_index(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return index;
+
+ auto path = *static_cast<const JsonPath*>(index.internal_data());
+ if (path.is_empty())
+ return {};
+
+ path.take_last();
+ if (path.is_empty())
+ return {};
+
+ auto* cpath = find_cached_path(path);
+ if (cpath) {
+ int index_in_parent = 0;
+ if (cpath->last().kind() == JsonPathElement::Kind::Index)
+ index_in_parent = cpath->last().index();
+ else if (cpath->last().kind() == JsonPathElement::Kind::Key) {
+ auto path_copy = path;
+ auto last = path_copy.take_last();
+ bool found = false;
+ path_copy.resolve(m_object.json).as_object().for_each_member([&](auto& name, auto&) {
+ if (!found) {
+ if (last.key() == name)
+ found = true;
+ else
+ index_in_parent++;
+ }
+ });
+ }
+ return create_index(index_in_parent, 0, cpath);
+ }
+
+ dbgln("No cached path found for path {}", path.to_string());
+ return {};
+}
+
+const JsonPath* RemoteObjectPropertyModel::cached_path_at(int n, const Vector<JsonPathElement>& prefix) const
+{
+ // FIXME: ModelIndex wants a void*, so we have to keep these
+ // indices alive, but allocating a new path every time
+ // we're asked for an index is silly, so we have to look for existing ones first.
+ const JsonPath* index_path = nullptr;
+ int row_index = n;
+ for (auto& path : m_paths) {
+ if (path.size() != prefix.size() + 1)
+ continue;
+
+ for (size_t i = 0; i < prefix.size(); ++i) {
+ if (path[i] != prefix[i])
+ goto do_continue;
+ }
+
+ if (row_index == 0) {
+ index_path = &path;
+ break;
+ }
+ --row_index;
+ do_continue:;
+ }
+
+ return index_path;
+};
+
+const JsonPath* RemoteObjectPropertyModel::find_cached_path(const Vector<JsonPathElement>& path) const
+{
+ for (auto& cpath : m_paths) {
+ if (cpath.size() != path.size())
+ continue;
+
+ for (size_t i = 0; i < cpath.size(); ++i) {
+ if (cpath[i] != path[i])
+ goto do_continue;
+ }
+
+ return &cpath;
+ do_continue:;
+ }
+
+ return nullptr;
+}
+
+}
diff --git a/Userland/DevTools/Inspector/RemoteObjectPropertyModel.h b/Userland/DevTools/Inspector/RemoteObjectPropertyModel.h
new file mode 100644
index 0000000000..f770fcf61e
--- /dev/null
+++ b/Userland/DevTools/Inspector/RemoteObjectPropertyModel.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/JsonPath.h>
+#include <AK/JsonValue.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <LibGUI/Model.h>
+
+namespace Inspector {
+
+class RemoteObject;
+
+class RemoteObjectPropertyModel final : public GUI::Model {
+public:
+ virtual ~RemoteObjectPropertyModel() override { }
+ static NonnullRefPtr<RemoteObjectPropertyModel> create(RemoteObject& object)
+ {
+ return adopt(*new RemoteObjectPropertyModel(object));
+ }
+
+ enum Column {
+ Name,
+ Value,
+ __Count,
+ };
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Count; }
+ virtual String column_name(int) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+ virtual void set_data(const GUI::ModelIndex&, const GUI::Variant&) override;
+ virtual void update() override;
+ virtual bool is_editable(const GUI::ModelIndex& index) const override { return index.column() == Column::Value; }
+ virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override;
+ virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override;
+
+private:
+ explicit RemoteObjectPropertyModel(RemoteObject&);
+
+ const JsonPath* cached_path_at(int n, const Vector<JsonPathElement>& prefix) const;
+ const JsonPath* find_cached_path(const Vector<JsonPathElement>& path) const;
+
+ RemoteObject& m_object;
+ mutable NonnullOwnPtrVector<JsonPath> m_paths;
+};
+
+}
diff --git a/Userland/DevTools/Inspector/RemoteProcess.cpp b/Userland/DevTools/Inspector/RemoteProcess.cpp
new file mode 100644
index 0000000000..aa36b46cae
--- /dev/null
+++ b/Userland/DevTools/Inspector/RemoteProcess.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RemoteProcess.h"
+#include "RemoteObject.h"
+#include "RemoteObjectGraphModel.h"
+#include "RemoteObjectPropertyModel.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace Inspector {
+
+RemoteProcess* s_the;
+
+RemoteProcess& RemoteProcess::the()
+{
+ return *s_the;
+}
+
+RemoteProcess::RemoteProcess(pid_t pid)
+ : m_pid(pid)
+ , m_object_graph_model(RemoteObjectGraphModel::create(*this))
+ , m_socket(Core::LocalSocket::construct())
+{
+ s_the = this;
+ m_socket->set_blocking(true);
+}
+
+void RemoteProcess::handle_identify_response(const JsonObject& response)
+{
+ int pid = response.get("pid").to_int();
+ ASSERT(pid == m_pid);
+
+ m_process_name = response.get("process_name").as_string_or({});
+
+ if (on_update)
+ on_update();
+}
+
+void RemoteProcess::handle_get_all_objects_response(const JsonObject& response)
+{
+ // FIXME: It would be good if we didn't have to make a local copy of the array value here!
+ auto objects = response.get("objects");
+ auto& object_array = objects.as_array();
+
+ NonnullOwnPtrVector<RemoteObject> remote_objects;
+ HashMap<FlatPtr, RemoteObject*> objects_by_address;
+
+ for (auto& value : object_array.values()) {
+ ASSERT(value.is_object());
+ auto& object = value.as_object();
+ auto remote_object = make<RemoteObject>();
+ remote_object->address = object.get("address").to_number<FlatPtr>();
+ remote_object->parent_address = object.get("parent").to_number<FlatPtr>();
+ remote_object->name = object.get("name").to_string();
+ remote_object->class_name = object.get("class_name").to_string();
+ remote_object->json = object;
+ objects_by_address.set(remote_object->address, remote_object);
+ remote_objects.append(move(remote_object));
+ }
+
+ for (size_t i = 0; i < remote_objects.size(); ++i) {
+ auto& remote_object = remote_objects.ptr_at(i);
+ auto* parent = objects_by_address.get(remote_object->parent_address).value_or(nullptr);
+ if (!parent) {
+ m_roots.append(move(remote_object));
+ } else {
+ remote_object->parent = parent;
+ parent->children.append(move(remote_object));
+ }
+ }
+
+ m_object_graph_model->update();
+
+ if (on_update)
+ on_update();
+}
+
+void RemoteProcess::send_request(const JsonObject& request)
+{
+ auto serialized = request.to_string();
+ i32 length = serialized.length();
+ m_socket->write((const u8*)&length, sizeof(length));
+ m_socket->write(serialized);
+}
+
+void RemoteProcess::set_inspected_object(FlatPtr address)
+{
+ JsonObject request;
+ request.set("type", "SetInspectedObject");
+ request.set("address", address);
+ send_request(request);
+}
+
+void RemoteProcess::set_property(FlatPtr object, const StringView& name, const JsonValue& value)
+{
+ JsonObject request;
+ request.set("type", "SetProperty");
+ request.set("address", object);
+ request.set("name", JsonValue(name));
+ request.set("value", value);
+ send_request(request);
+}
+
+void RemoteProcess::update()
+{
+ m_socket->on_connected = [this] {
+ dbgln("Connected to PID {}", m_pid);
+
+ {
+ JsonObject request;
+ request.set("type", "Identify");
+ send_request(request);
+ }
+
+ {
+ JsonObject request;
+ request.set("type", "GetAllObjects");
+ send_request(request);
+ }
+ };
+
+ m_socket->on_ready_to_read = [this] {
+ if (m_socket->eof()) {
+ dbgln("Disconnected from PID {}", m_pid);
+ m_socket->close();
+ return;
+ }
+
+ u32 length;
+ int nread = m_socket->read((u8*)&length, sizeof(length));
+ ASSERT(nread == sizeof(length));
+
+ ByteBuffer data;
+ size_t remaining_bytes = length;
+
+ while (remaining_bytes) {
+ auto packet = m_socket->read(remaining_bytes);
+ if (packet.size() == 0)
+ break;
+ data.append(packet.data(), packet.size());
+ remaining_bytes -= packet.size();
+ }
+
+ ASSERT(data.size() == length);
+ dbgln("Got data size {} and read that many bytes", length);
+
+ auto json_value = JsonValue::from_string(data);
+ ASSERT(json_value.has_value());
+ ASSERT(json_value.value().is_object());
+
+ dbgln("Got JSON response {}", json_value.value());
+
+ auto& response = json_value.value().as_object();
+
+ auto response_type = response.get("type").as_string_or({});
+ if (response_type.is_null())
+ return;
+
+ if (response_type == "GetAllObjects") {
+ handle_get_all_objects_response(response);
+ return;
+ }
+
+ if (response_type == "Identify") {
+ handle_identify_response(response);
+ return;
+ }
+ };
+
+ auto success = m_socket->connect(Core::SocketAddress::local(String::format("/tmp/rpc/%d", m_pid)));
+ if (!success) {
+ warnln("Couldn't connect to PID {}", m_pid);
+ exit(1);
+ }
+}
+
+}
diff --git a/Userland/DevTools/Inspector/RemoteProcess.h b/Userland/DevTools/Inspector/RemoteProcess.h
new file mode 100644
index 0000000000..427b7183b5
--- /dev/null
+++ b/Userland/DevTools/Inspector/RemoteProcess.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtrVector.h>
+#include <LibCore/LocalSocket.h>
+
+namespace Inspector {
+
+class RemoteObjectGraphModel;
+class RemoteObject;
+
+class RemoteProcess {
+public:
+ static RemoteProcess& the();
+
+ explicit RemoteProcess(pid_t);
+ void update();
+
+ pid_t pid() const { return m_pid; }
+ const String& process_name() const { return m_process_name; }
+
+ RemoteObjectGraphModel& object_graph_model() { return *m_object_graph_model; }
+ const NonnullOwnPtrVector<RemoteObject>& roots() const { return m_roots; }
+
+ void set_inspected_object(FlatPtr);
+
+ void set_property(FlatPtr object, const StringView& name, const JsonValue& value);
+
+ Function<void()> on_update;
+
+private:
+ void handle_get_all_objects_response(const AK::JsonObject&);
+ void handle_identify_response(const AK::JsonObject&);
+
+ void send_request(const AK::JsonObject&);
+
+ pid_t m_pid { -1 };
+ String m_process_name;
+ NonnullRefPtr<RemoteObjectGraphModel> m_object_graph_model;
+ RefPtr<Core::LocalSocket> m_socket;
+ NonnullOwnPtrVector<RemoteObject> m_roots;
+};
+
+}
diff --git a/Userland/DevTools/Inspector/main.cpp b/Userland/DevTools/Inspector/main.cpp
new file mode 100644
index 0000000000..19819b725d
--- /dev/null
+++ b/Userland/DevTools/Inspector/main.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "RemoteObject.h"
+#include "RemoteObjectGraphModel.h"
+#include "RemoteObjectPropertyModel.h"
+#include "RemoteProcess.h"
+#include <LibGUI/Application.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/MenuBar.h>
+#include <LibGUI/ModelEditingDelegate.h>
+#include <LibGUI/ProcessChooser.h>
+#include <LibGUI/Splitter.h>
+#include <LibGUI/TreeView.h>
+#include <LibGUI/Window.h>
+#include <stdio.h>
+
+using namespace Inspector;
+
+[[noreturn]] static void print_usage_and_exit()
+{
+ outln("usage: Inspector <pid>");
+ exit(0);
+}
+
+int main(int argc, char** argv)
+{
+ if (pledge("stdio shared_buffer rpath accept unix cpath fattr", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ if (unveil("/res", "r") < 0) {
+ perror("unveil");
+ return 1;
+ }
+
+ if (unveil("/bin", "r") < 0) {
+ perror("unveil");
+ return 1;
+ }
+
+ if (unveil("/tmp", "rwc") < 0) {
+ perror("unveil");
+ return 1;
+ }
+
+ if (unveil("/proc/all", "r") < 0) {
+ perror("unveil");
+ return 1;
+ }
+
+ if (unveil("/etc/passwd", "r") < 0) {
+ perror("unveil");
+ return 1;
+ }
+
+ unveil(nullptr, nullptr);
+
+ pid_t pid;
+
+ auto app = GUI::Application::construct(argc, argv);
+ auto app_icon = GUI::Icon::default_icon("app-inspector");
+ if (argc != 2) {
+ auto process_chooser = GUI::ProcessChooser::construct("Inspector", "Inspect", app_icon.bitmap_for_size(16));
+ if (process_chooser->exec() == GUI::Dialog::ExecCancel)
+ return 0;
+ pid = process_chooser->pid();
+ } else {
+ auto pid_opt = String(argv[1]).to_int();
+ if (!pid_opt.has_value())
+ print_usage_and_exit();
+ pid = pid_opt.value();
+ }
+
+ auto window = GUI::Window::construct();
+ window->set_title("Inspector");
+ window->resize(685, 500);
+ window->set_icon(app_icon.bitmap_for_size(16));
+
+ auto menubar = GUI::MenuBar::construct();
+ auto& app_menu = menubar->add_menu("Inspector");
+ app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); }));
+
+ auto& help_menu = menubar->add_menu("Help");
+ help_menu.add_action(GUI::CommonActions::make_about_action("Inspector", app_icon, window));
+
+ auto& widget = window->set_main_widget<GUI::Widget>();
+ widget.set_fill_with_background_color(true);
+ widget.set_layout<GUI::VerticalBoxLayout>();
+
+ auto& splitter = widget.add<GUI::HorizontalSplitter>();
+
+ RemoteProcess remote_process(pid);
+
+ remote_process.on_update = [&] {
+ if (!remote_process.process_name().is_null())
+ window->set_title(String::formatted("{} ({}) - Inspector", remote_process.process_name(), remote_process.pid()));
+ };
+
+ auto& tree_view = splitter.add<GUI::TreeView>();
+ tree_view.set_model(remote_process.object_graph_model());
+ tree_view.set_activates_on_selection(true);
+ tree_view.set_fixed_width(286);
+
+ auto& properties_tree_view = splitter.add<GUI::TreeView>();
+ properties_tree_view.set_editable(true);
+ properties_tree_view.aid_create_editing_delegate = [](auto&) {
+ return make<GUI::StringModelEditingDelegate>();
+ };
+
+ tree_view.on_activation = [&](auto& index) {
+ auto* remote_object = static_cast<RemoteObject*>(index.internal_data());
+ properties_tree_view.set_model(remote_object->property_model());
+ remote_process.set_inspected_object(remote_object->address);
+ };
+
+ app->set_menubar(move(menubar));
+ window->show();
+ remote_process.update();
+
+ if (pledge("stdio shared_buffer rpath accept unix", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ return app->exec();
+}
diff --git a/Userland/DevTools/Playground/CMakeLists.txt b/Userland/DevTools/Playground/CMakeLists.txt
new file mode 100644
index 0000000000..e0fa186766
--- /dev/null
+++ b/Userland/DevTools/Playground/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(SOURCES
+ main.cpp
+)
+
+serenity_app(Playground ICON app-playground)
+target_link_libraries(Playground LibDesktop LibGUI)
diff --git a/Userland/DevTools/Playground/main.cpp b/Userland/DevTools/Playground/main.cpp
new file mode 100644
index 0000000000..8ed1cdbda8
--- /dev/null
+++ b/Userland/DevTools/Playground/main.cpp
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/QuickSort.h>
+#include <AK/URL.h>
+#include <LibCore/ArgsParser.h>
+#include <LibCore/File.h>
+#include <LibCore/Property.h>
+#include <LibDesktop/Launcher.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/AutocompleteProvider.h>
+#include <LibGUI/FilePicker.h>
+#include <LibGUI/GMLFormatter.h>
+#include <LibGUI/GMLLexer.h>
+#include <LibGUI/GMLSyntaxHighlighter.h>
+#include <LibGUI/Icon.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/MenuBar.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Splitter.h>
+#include <LibGUI/TextEditor.h>
+#include <LibGUI/Window.h>
+#include <string.h>
+
+namespace {
+
+class UnregisteredWidget final : public GUI::Widget {
+ C_OBJECT(UnregisteredWidget);
+
+private:
+ UnregisteredWidget(const String& class_name);
+
+ virtual void paint_event(GUI::PaintEvent& event) override;
+
+ String m_text;
+};
+
+UnregisteredWidget::UnregisteredWidget(const String& class_name)
+{
+ StringBuilder builder;
+ builder.append(class_name);
+ builder.append("\nnot registered");
+ m_text = builder.to_string();
+}
+
+void UnregisteredWidget::paint_event(GUI::PaintEvent& event)
+{
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ painter.fill_rect(event.rect(), Gfx::Color::DarkRed);
+ painter.draw_text(rect(), m_text, Gfx::TextAlignment::Center, Color::White);
+}
+
+}
+
+class GMLAutocompleteProvider final : public virtual GUI::AutocompleteProvider {
+public:
+ GMLAutocompleteProvider() { }
+ virtual ~GMLAutocompleteProvider() override { }
+
+private:
+ static bool can_have_declared_layout(const StringView& class_name)
+ {
+ return class_name.is_one_of("GUI::Widget", "GUI::Frame");
+ }
+
+ virtual void provide_completions(Function<void(Vector<Entry>)> callback) override
+ {
+ auto cursor = m_editor->cursor();
+ auto text = m_editor->text();
+ GUI::GMLLexer lexer(text);
+ // FIXME: Provide a begin() and end() for lexers PLEASE!
+ auto all_tokens = lexer.lex();
+ enum State {
+ Free,
+ InClassName,
+ AfterClassName,
+ InIdentifier,
+ AfterIdentifier, // Can we introspect this?
+ } state { Free };
+ String identifier_string;
+ Vector<String> class_names;
+ Vector<State> previous_states;
+ bool should_push_state { true };
+ GUI::GMLToken* last_seen_token { nullptr };
+
+ for (auto& token : all_tokens) {
+ if (token.m_start.line > cursor.line() || (token.m_start.line == cursor.line() && token.m_start.column > cursor.column()))
+ break;
+
+ last_seen_token = &token;
+ switch (state) {
+ case Free:
+ if (token.m_type == GUI::GMLToken::Type::ClassName) {
+ if (should_push_state)
+ previous_states.append(state);
+ else
+ should_push_state = true;
+ state = InClassName;
+ class_names.append(token.m_view);
+ break;
+ }
+ break;
+ case InClassName:
+ state = AfterClassName;
+ break;
+ case AfterClassName:
+ if (token.m_type == GUI::GMLToken::Type::Identifier) {
+ state = InIdentifier;
+ identifier_string = token.m_view;
+ break;
+ }
+ if (token.m_type == GUI::GMLToken::Type::RightCurly) {
+ class_names.take_last();
+ state = previous_states.take_last();
+ break;
+ }
+ if (token.m_type == GUI::GMLToken::Type::ClassMarker) {
+ previous_states.append(AfterClassName);
+ state = Free;
+ should_push_state = false;
+ }
+ break;
+ case InIdentifier:
+ if (token.m_type == GUI::GMLToken::Type::Colon)
+ state = AfterIdentifier;
+ break;
+ case AfterIdentifier:
+ if (token.m_type == GUI::GMLToken::Type::RightCurly || token.m_type == GUI::GMLToken::Type::LeftCurly)
+ break;
+ if (token.m_type == GUI::GMLToken::Type::ClassMarker) {
+ previous_states.append(AfterClassName);
+ state = Free;
+ should_push_state = false;
+ } else {
+ state = AfterClassName;
+ }
+ break;
+ }
+ }
+
+ Vector<GUI::AutocompleteProvider::Entry> class_entries, identifier_entries;
+ switch (state) {
+ case Free:
+ if (last_seen_token && last_seen_token->m_end.column + 1 != cursor.column() && last_seen_token->m_end.line == cursor.line()) {
+ // After some token, but with extra space, not on a new line.
+ // Nothing to put here.
+ break;
+ }
+ GUI::WidgetClassRegistration::for_each([&](const GUI::WidgetClassRegistration& registration) {
+ class_entries.empend(String::formatted("@{}", registration.class_name()), 0u);
+ });
+ break;
+ case InClassName:
+ if (class_names.is_empty())
+ break;
+ if (last_seen_token && last_seen_token->m_end.column + 1 != cursor.column() && last_seen_token->m_end.line == cursor.line()) {
+ // After a class name, but haven't seen braces.
+ // TODO: Suggest braces?
+ break;
+ }
+ GUI::WidgetClassRegistration::for_each([&](const GUI::WidgetClassRegistration& registration) {
+ if (registration.class_name().starts_with(class_names.last()))
+ identifier_entries.empend(registration.class_name(), class_names.last().length());
+ });
+ break;
+ case InIdentifier:
+ if (class_names.is_empty())
+ break;
+ if (last_seen_token && last_seen_token->m_end.column + 1 != cursor.column() && last_seen_token->m_end.line == cursor.line()) {
+ // After an identifier, but with extra space
+ // TODO: Maybe suggest a colon?
+ break;
+ }
+ if (auto registration = GUI::WidgetClassRegistration::find(class_names.last())) {
+ auto instance = registration->construct();
+ for (auto& it : instance->properties()) {
+ if (it.key.starts_with(identifier_string))
+ identifier_entries.empend(it.key, identifier_string.length());
+ }
+ }
+ if (can_have_declared_layout(class_names.last()) && StringView { "layout" }.starts_with(identifier_string))
+ identifier_entries.empend("layout", identifier_string.length());
+ // No need to suggest anything if it's already completely typed out!
+ if (identifier_entries.size() == 1 && identifier_entries.first().completion == identifier_string)
+ identifier_entries.clear();
+ break;
+ case AfterClassName:
+ if (last_seen_token && last_seen_token->m_end.line == cursor.line()) {
+ if (last_seen_token->m_type != GUI::GMLToken::Type::Identifier || last_seen_token->m_end.column + 1 != cursor.column()) {
+ // Inside braces, but on the same line as some other stuff (and not the continuation of one!)
+ // The user expects nothing here.
+ break;
+ }
+ }
+ if (!class_names.is_empty()) {
+ if (auto registration = GUI::WidgetClassRegistration::find(class_names.last())) {
+ auto instance = registration->construct();
+ for (auto& it : instance->properties()) {
+ if (!it.value->is_readonly())
+ identifier_entries.empend(it.key, 0u);
+ }
+ }
+ }
+ GUI::WidgetClassRegistration::for_each([&](const GUI::WidgetClassRegistration& registration) {
+ class_entries.empend(String::formatted("@{}", registration.class_name()), 0u);
+ });
+ break;
+ case AfterIdentifier:
+ if (last_seen_token && last_seen_token->m_end.line != cursor.line()) {
+ break;
+ }
+ if (identifier_string == "layout") {
+ GUI::WidgetClassRegistration::for_each([&](const GUI::WidgetClassRegistration& registration) {
+ if (registration.class_name().contains("Layout"))
+ class_entries.empend(String::formatted("@{}", registration.class_name()), 0u);
+ });
+ }
+ break;
+ default:
+ break;
+ }
+
+ quick_sort(class_entries, [](auto& a, auto& b) { return a.completion < b.completion; });
+ quick_sort(identifier_entries, [](auto& a, auto& b) { return a.completion < b.completion; });
+
+ Vector<GUI::AutocompleteProvider::Entry> entries;
+ entries.append(move(identifier_entries));
+ entries.append(move(class_entries));
+
+ callback(move(entries));
+ }
+};
+
+int main(int argc, char** argv)
+{
+ if (pledge("stdio thread shared_buffer accept cpath rpath wpath unix fattr", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ auto app = GUI::Application::construct(argc, argv);
+
+ if (pledge("stdio thread shared_buffer accept rpath cpath wpath unix", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ if (!Desktop::Launcher::add_allowed_handler_with_only_specific_urls(
+ "/bin/Help",
+ { URL::create_with_file_protocol("/usr/share/man/man1/Playground.md") })
+ || !Desktop::Launcher::seal_allowlist()) {
+ warnln("Failed to set up allowed launch URLs");
+ return 1;
+ }
+
+ if (pledge("stdio thread shared_buffer accept rpath cpath wpath", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ const char* path = nullptr;
+ Core::ArgsParser args_parser;
+ args_parser.add_positional_argument(path, "GML file to edit", "file", Core::ArgsParser::Required::No);
+ args_parser.parse(argc, argv);
+
+ auto app_icon = GUI::Icon::default_icon("app-playground");
+ auto window = GUI::Window::construct();
+ window->set_title("GML Playground");
+ window->set_icon(app_icon.bitmap_for_size(16));
+ window->resize(800, 600);
+
+ auto& splitter = window->set_main_widget<GUI::HorizontalSplitter>();
+
+ auto& editor = splitter.add<GUI::TextEditor>();
+ auto& preview = splitter.add<GUI::Widget>();
+
+ editor.set_syntax_highlighter(make<GUI::GMLSyntaxHighlighter>());
+ editor.set_autocomplete_provider(make<GMLAutocompleteProvider>());
+ editor.set_should_autocomplete_automatically(true);
+ editor.set_automatic_indentation_enabled(true);
+
+ if (String(path).is_empty()) {
+ editor.set_text(R"~~~(@GUI::Widget {
+ layout: @GUI::VerticalBoxLayout {
+ }
+
+ // Now add some widgets!
+}
+)~~~");
+ editor.set_cursor(4, 28); // after "...widgets!"
+ } else {
+ auto file = Core::File::construct(path);
+ if (!file->open(Core::IODevice::ReadOnly)) {
+ GUI::MessageBox::show(window, String::formatted("Opening \"{}\" failed: {}", path, strerror(errno)), "Error", GUI::MessageBox::Type::Error);
+ return 1;
+ }
+ editor.set_text(file->read_all());
+ }
+
+ editor.on_change = [&] {
+ preview.remove_all_children();
+ preview.load_from_gml(editor.text(), [](const String& class_name) -> RefPtr<GUI::Widget> {
+ return UnregisteredWidget::construct(class_name);
+ });
+ };
+
+ auto menubar = GUI::MenuBar::construct();
+ auto& app_menu = menubar->add_menu("GML Playground");
+
+ app_menu.add_action(GUI::CommonActions::make_open_action([&](auto&) {
+ Optional<String> open_path = GUI::FilePicker::get_open_filepath(window);
+
+ if (!open_path.has_value())
+ return;
+
+ auto file = Core::File::construct(open_path.value());
+ if (!file->open(Core::IODevice::ReadOnly) && file->error() != ENOENT) {
+ GUI::MessageBox::show(window, String::formatted("Opening \"{}\" failed: {}", open_path.value(), strerror(errno)), "Error", GUI::MessageBox::Type::Error);
+ return;
+ }
+
+ editor.set_text(file->read_all());
+ editor.set_focus(true);
+ }));
+
+ app_menu.add_action(GUI::CommonActions::make_save_as_action([&](auto&) {
+ Optional<String> save_path = GUI::FilePicker::get_save_filepath(window, "Untitled", "gml");
+ if (!save_path.has_value())
+ return;
+
+ if (!editor.write_to_file(save_path.value())) {
+ GUI::MessageBox::show(window, "Unable to save file.\n", "Error", GUI::MessageBox::Type::Error);
+ return;
+ }
+ }));
+
+ app_menu.add_separator();
+
+ app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) {
+ app->quit();
+ }));
+
+ auto& edit_menu = menubar->add_menu("Edit");
+ edit_menu.add_action(GUI::Action::create("Format GML", { Mod_Ctrl | Mod_Shift, Key_I }, [&](auto&) {
+ auto source = editor.text();
+ GUI::GMLLexer lexer(source);
+ for (auto& token : lexer.lex()) {
+ if (token.m_type == GUI::GMLToken::Type::Comment) {
+ auto result = GUI::MessageBox::show(
+ window,
+ "Your GML contains comments, which currently are not supported by the formatter and will be removed. Proceed?",
+ "Warning",
+ GUI::MessageBox::Type::Warning,
+ GUI::MessageBox::InputType::OKCancel);
+ if (result == GUI::MessageBox::ExecCancel)
+ return;
+ break;
+ }
+ }
+ auto formatted_gml = GUI::format_gml(source);
+ if (!formatted_gml.is_null()) {
+ editor.set_text(formatted_gml);
+ } else {
+ GUI::MessageBox::show(
+ window,
+ "GML could not be formatted, please check the debug console for parsing errors.",
+ "Error",
+ GUI::MessageBox::Type::Error);
+ }
+ }));
+
+ auto& help_menu = menubar->add_menu("Help");
+ help_menu.add_action(GUI::CommonActions::make_help_action([](auto&) {
+ Desktop::Launcher::open(URL::create_with_file_protocol("/usr/share/man/man1/Playground.md"), "/bin/Help");
+ }));
+ help_menu.add_action(GUI::CommonActions::make_about_action("GML Playground", app_icon, window));
+
+ app->set_menubar(move(menubar));
+
+ window->show();
+ return app->exec();
+}
diff --git a/Userland/DevTools/Profiler/CMakeLists.txt b/Userland/DevTools/Profiler/CMakeLists.txt
new file mode 100644
index 0000000000..1cb7b95140
--- /dev/null
+++ b/Userland/DevTools/Profiler/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES
+ DisassemblyModel.cpp
+ main.cpp
+ Profile.cpp
+ ProfileModel.cpp
+ ProfileTimelineWidget.cpp
+)
+
+serenity_app(Profiler ICON app-profiler)
+target_link_libraries(Profiler LibGUI LibX86 LibCoreDump)
diff --git a/Userland/DevTools/Profiler/DisassemblyModel.cpp b/Userland/DevTools/Profiler/DisassemblyModel.cpp
new file mode 100644
index 0000000000..4195f103cd
--- /dev/null
+++ b/Userland/DevTools/Profiler/DisassemblyModel.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DisassemblyModel.h"
+#include "Profile.h"
+#include <AK/MappedFile.h>
+#include <LibELF/Image.h>
+#include <LibGUI/Painter.h>
+#include <LibX86/Disassembler.h>
+#include <LibX86/ELFSymbolProvider.h>
+#include <ctype.h>
+#include <stdio.h>
+
+static const Gfx::Bitmap& heat_gradient()
+{
+ static RefPtr<Gfx::Bitmap> bitmap;
+ if (!bitmap) {
+ bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, { 101, 1 });
+ GUI::Painter painter(*bitmap);
+ painter.fill_rect_with_gradient(Orientation::Horizontal, bitmap->rect(), Color::from_rgb(0xffc080), Color::from_rgb(0xff3000));
+ }
+ return *bitmap;
+}
+
+static Color color_for_percent(int percent)
+{
+ ASSERT(percent >= 0 && percent <= 100);
+ return heat_gradient().get_pixel(percent, 0);
+}
+
+DisassemblyModel::DisassemblyModel(Profile& profile, ProfileNode& node)
+ : m_profile(profile)
+ , m_node(node)
+{
+ OwnPtr<ELF::Image> kernel_elf;
+ const ELF::Image* elf;
+ FlatPtr base_address = 0;
+ if (m_node.address() >= 0xc0000000) {
+ if (!m_kernel_file) {
+ auto file_or_error = MappedFile::map("/boot/Kernel");
+ if (file_or_error.is_error())
+ return;
+ m_kernel_file = file_or_error.release_value();
+ }
+ kernel_elf = make<ELF::Image>((const u8*)m_kernel_file->data(), m_kernel_file->size());
+ elf = kernel_elf.ptr();
+ } else {
+ auto library_data = profile.libraries().library_containing(node.address());
+ if (!library_data) {
+ dbgln("no library data");
+ return;
+ }
+ elf = &library_data->elf;
+ base_address = library_data->base;
+ }
+
+ ASSERT(elf != nullptr);
+
+ auto symbol = elf->find_symbol(node.address() - base_address);
+ if (!symbol.has_value()) {
+ dbgln("DisassemblyModel: symbol not found");
+ return;
+ }
+ ASSERT(symbol.has_value());
+
+ auto view = symbol.value().raw_data();
+
+ X86::ELFSymbolProvider symbol_provider(*elf);
+ X86::SimpleInstructionStream stream((const u8*)view.characters_without_null_termination(), view.length());
+ X86::Disassembler disassembler(stream);
+
+ size_t offset_into_symbol = 0;
+ for (;;) {
+ auto insn = disassembler.next();
+ if (!insn.has_value())
+ break;
+ FlatPtr address_in_profiled_program = symbol.value().value() + offset_into_symbol;
+
+ auto disassembly = insn.value().to_string(address_in_profiled_program, &symbol_provider);
+
+ StringView instruction_bytes = view.substring_view(offset_into_symbol, insn.value().length());
+ size_t samples_at_this_instruction = m_node.events_per_address().get(address_in_profiled_program).value_or(0);
+ float percent = ((float)samples_at_this_instruction / (float)m_node.event_count()) * 100.0f;
+
+ m_instructions.append({ insn.value(), disassembly, instruction_bytes, address_in_profiled_program, samples_at_this_instruction, percent });
+
+ offset_into_symbol += insn.value().length();
+ }
+}
+
+DisassemblyModel::~DisassemblyModel()
+{
+}
+
+int DisassemblyModel::row_count(const GUI::ModelIndex&) const
+{
+ return m_instructions.size();
+}
+
+String DisassemblyModel::column_name(int column) const
+{
+ switch (column) {
+ case Column::SampleCount:
+ return m_profile.show_percentages() ? "% Samples" : "# Samples";
+ case Column::Address:
+ return "Address";
+ case Column::InstructionBytes:
+ return "Insn Bytes";
+ case Column::Disassembly:
+ return "Disassembly";
+ default:
+ ASSERT_NOT_REACHED();
+ return {};
+ }
+}
+
+struct ColorPair {
+ Color background;
+ Color foreground;
+};
+
+static Optional<ColorPair> color_pair_for(const InstructionData& insn)
+{
+ if (insn.percent == 0)
+ return {};
+
+ Color background = color_for_percent(insn.percent);
+ Color foreground;
+ if (insn.percent > 50)
+ foreground = Color::White;
+ else
+ foreground = Color::Black;
+ return ColorPair { background, foreground };
+}
+
+GUI::Variant DisassemblyModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto& insn = m_instructions[index.row()];
+
+ if (role == GUI::ModelRole::BackgroundColor) {
+ auto colors = color_pair_for(insn);
+ if (!colors.has_value())
+ return {};
+ return colors.value().background;
+ }
+
+ if (role == GUI::ModelRole::ForegroundColor) {
+ auto colors = color_pair_for(insn);
+ if (!colors.has_value())
+ return {};
+ return colors.value().foreground;
+ }
+
+ if (role == GUI::ModelRole::Display) {
+ if (index.column() == Column::SampleCount) {
+ if (m_profile.show_percentages())
+ return ((float)insn.event_count / (float)m_node.event_count()) * 100.0f;
+ return insn.event_count;
+ }
+ if (index.column() == Column::Address)
+ return String::formatted("{:p}", insn.address);
+ if (index.column() == Column::InstructionBytes) {
+ StringBuilder builder;
+ for (auto ch : insn.bytes) {
+ builder.appendff("{:02x} ", (u8)ch);
+ }
+ return builder.to_string();
+ }
+ if (index.column() == Column::Disassembly)
+ return insn.disassembly;
+ return {};
+ }
+ return {};
+}
+
+void DisassemblyModel::update()
+{
+ did_update();
+}
diff --git a/Userland/DevTools/Profiler/DisassemblyModel.h b/Userland/DevTools/Profiler/DisassemblyModel.h
new file mode 100644
index 0000000000..0d0e643292
--- /dev/null
+++ b/Userland/DevTools/Profiler/DisassemblyModel.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Model.h>
+#include <LibX86/Instruction.h>
+
+class Profile;
+class ProfileNode;
+
+struct InstructionData {
+ X86::Instruction insn;
+ String disassembly;
+ StringView bytes;
+ FlatPtr address { 0 };
+ u32 event_count { 0 };
+ float percent { 0 };
+};
+
+class DisassemblyModel final : public GUI::Model {
+public:
+ static NonnullRefPtr<DisassemblyModel> create(Profile& profile, ProfileNode& node)
+ {
+ return adopt(*new DisassemblyModel(profile, node));
+ }
+
+ enum Column {
+ Address,
+ SampleCount,
+ InstructionBytes,
+ Disassembly,
+ __Count
+ };
+
+ virtual ~DisassemblyModel() override;
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Count; }
+ virtual String column_name(int) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+ virtual void update() override;
+
+private:
+ DisassemblyModel(Profile&, ProfileNode&);
+
+ Profile& m_profile;
+ ProfileNode& m_node;
+ RefPtr<MappedFile> m_kernel_file;
+
+ Vector<InstructionData> m_instructions;
+};
diff --git a/Userland/DevTools/Profiler/Profile.cpp b/Userland/DevTools/Profiler/Profile.cpp
new file mode 100644
index 0000000000..8e8e986841
--- /dev/null
+++ b/Userland/DevTools/Profiler/Profile.cpp
@@ -0,0 +1,399 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Profile.h"
+#include "DisassemblyModel.h"
+#include "ProfileModel.h"
+#include <AK/HashTable.h>
+#include <AK/MappedFile.h>
+#include <AK/QuickSort.h>
+#include <AK/RefPtr.h>
+#include <LibCore/File.h>
+#include <LibELF/Image.h>
+#include <stdio.h>
+#include <sys/stat.h>
+
+static void sort_profile_nodes(Vector<NonnullRefPtr<ProfileNode>>& nodes)
+{
+ quick_sort(nodes.begin(), nodes.end(), [](auto& a, auto& b) {
+ return a->event_count() >= b->event_count();
+ });
+
+ for (auto& child : nodes)
+ child->sort_children();
+}
+
+Profile::Profile(String executable_path, Vector<Event> events, NonnullOwnPtr<LibraryMetadata> library_metadata)
+ : m_executable_path(move(executable_path))
+ , m_events(move(events))
+ , m_library_metadata(move(library_metadata))
+{
+ m_first_timestamp = m_events.first().timestamp;
+ m_last_timestamp = m_events.last().timestamp;
+
+ m_model = ProfileModel::create(*this);
+
+ for (auto& event : m_events) {
+ m_deepest_stack_depth = max((u32)event.frames.size(), m_deepest_stack_depth);
+ }
+
+ rebuild_tree();
+}
+
+Profile::~Profile()
+{
+}
+
+GUI::Model& Profile::model()
+{
+ return *m_model;
+}
+
+void Profile::rebuild_tree()
+{
+ u32 filtered_event_count = 0;
+ Vector<NonnullRefPtr<ProfileNode>> roots;
+
+ auto find_or_create_root = [&roots](const String& symbol, u32 address, u32 offset, u64 timestamp) -> ProfileNode& {
+ for (size_t i = 0; i < roots.size(); ++i) {
+ auto& root = roots[i];
+ if (root->symbol() == symbol) {
+ return root;
+ }
+ }
+ auto new_root = ProfileNode::create(symbol, address, offset, timestamp);
+ roots.append(new_root);
+ return new_root;
+ };
+
+ HashTable<FlatPtr> live_allocations;
+
+ for (auto& event : m_events) {
+ if (has_timestamp_filter_range()) {
+ auto timestamp = event.timestamp;
+ if (timestamp < m_timestamp_filter_range_start || timestamp > m_timestamp_filter_range_end)
+ continue;
+ }
+
+ if (event.type == "malloc")
+ live_allocations.set(event.ptr);
+ else if (event.type == "free")
+ live_allocations.remove(event.ptr);
+ }
+
+ for (size_t event_index = 0; event_index < m_events.size(); ++event_index) {
+ auto& event = m_events.at(event_index);
+ if (has_timestamp_filter_range()) {
+ auto timestamp = event.timestamp;
+ if (timestamp < m_timestamp_filter_range_start || timestamp > m_timestamp_filter_range_end)
+ continue;
+ }
+
+ if (event.type == "malloc" && !live_allocations.contains(event.ptr))
+ continue;
+
+ if (event.type == "free")
+ continue;
+
+ auto for_each_frame = [&]<typename Callback>(Callback callback) {
+ if (!m_inverted) {
+ for (size_t i = 0; i < event.frames.size(); ++i) {
+ if (callback(event.frames.at(i), i == event.frames.size() - 1) == IterationDecision::Break)
+ break;
+ }
+ } else {
+ for (ssize_t i = event.frames.size() - 1; i >= 0; --i) {
+ if (callback(event.frames.at(i), static_cast<size_t>(i) == event.frames.size() - 1) == IterationDecision::Break)
+ break;
+ }
+ }
+ };
+
+ if (!m_show_top_functions) {
+ ProfileNode* node = nullptr;
+ for_each_frame([&](const Frame& frame, bool is_innermost_frame) {
+ auto& symbol = frame.symbol;
+ auto& address = frame.address;
+ auto& offset = frame.offset;
+
+ if (symbol.is_empty())
+ return IterationDecision::Break;
+
+ if (!node)
+ node = &find_or_create_root(symbol, address, offset, event.timestamp);
+ else
+ node = &node->find_or_create_child(symbol, address, offset, event.timestamp);
+
+ node->increment_event_count();
+ if (is_innermost_frame) {
+ node->add_event_address(address);
+ node->increment_self_count();
+ }
+ return IterationDecision::Continue;
+ });
+ } else {
+ for (size_t i = 0; i < event.frames.size(); ++i) {
+ ProfileNode* node = nullptr;
+ ProfileNode* root = nullptr;
+ for (size_t j = i; j < event.frames.size(); ++j) {
+ auto& frame = event.frames.at(j);
+ auto& symbol = frame.symbol;
+ auto& address = frame.address;
+ auto& offset = frame.offset;
+ if (symbol.is_empty())
+ break;
+
+ if (!node) {
+ node = &find_or_create_root(symbol, address, offset, event.timestamp);
+ root = node;
+ root->will_track_seen_events(m_events.size());
+ } else {
+ node = &node->find_or_create_child(symbol, address, offset, event.timestamp);
+ }
+
+ if (!root->has_seen_event(event_index)) {
+ root->did_see_event(event_index);
+ root->increment_event_count();
+ } else if (node != root) {
+ node->increment_event_count();
+ }
+
+ if (j == event.frames.size() - 1) {
+ node->add_event_address(address);
+ node->increment_self_count();
+ }
+ }
+ }
+ }
+
+ ++filtered_event_count;
+ }
+
+ sort_profile_nodes(roots);
+
+ m_filtered_event_count = filtered_event_count;
+ m_roots = move(roots);
+ m_model->update();
+}
+
+Result<NonnullOwnPtr<Profile>, String> Profile::load_from_perfcore_file(const StringView& path)
+{
+ auto file = Core::File::construct(path);
+ if (!file->open(Core::IODevice::ReadOnly))
+ return String::formatted("Unable to open {}, error: {}", path, file->error_string());
+
+ auto json = JsonValue::from_string(file->read_all());
+ if (!json.has_value() || !json.value().is_object())
+ return String { "Invalid perfcore format (not a JSON object)" };
+
+ auto& object = json.value().as_object();
+ auto executable_path = object.get("executable").to_string();
+
+ auto pid = object.get("pid");
+ if (!pid.is_u32())
+ return String { "Invalid perfcore format (no process ID)" };
+
+ auto file_or_error = MappedFile::map("/boot/Kernel");
+ OwnPtr<ELF::Image> kernel_elf;
+ if (!file_or_error.is_error())
+ kernel_elf = make<ELF::Image>(file_or_error.value()->bytes());
+
+ auto events_value = object.get("events");
+ if (!events_value.is_array())
+ return String { "Malformed profile (events is not an array)" };
+
+ auto regions_value = object.get("regions");
+ if (!regions_value.is_array() || regions_value.as_array().is_empty())
+ return String { "Malformed profile (regions is not an array, or it is empty)" };
+
+ auto& perf_events = events_value.as_array();
+ if (perf_events.is_empty())
+ return String { "No events captured (targeted process was never on CPU)" };
+
+ auto library_metadata = make<LibraryMetadata>(regions_value.as_array());
+
+ Vector<Event> events;
+
+ for (auto& perf_event_value : perf_events.values()) {
+ auto& perf_event = perf_event_value.as_object();
+
+ Event event;
+
+ event.timestamp = perf_event.get("timestamp").to_number<u64>();
+ event.type = perf_event.get("type").to_string();
+
+ if (event.type == "malloc") {
+ event.ptr = perf_event.get("ptr").to_number<FlatPtr>();
+ event.size = perf_event.get("size").to_number<size_t>();
+ } else if (event.type == "free") {
+ event.ptr = perf_event.get("ptr").to_number<FlatPtr>();
+ }
+
+ auto stack_array = perf_event.get("stack").as_array();
+ for (ssize_t i = stack_array.values().size() - 1; i >= 0; --i) {
+ auto& frame = stack_array.at(i);
+ auto ptr = frame.to_number<u32>();
+ u32 offset = 0;
+ String symbol;
+
+ if (ptr >= 0xc0000000) {
+ if (kernel_elf) {
+ symbol = kernel_elf->symbolicate(ptr, &offset);
+ } else {
+ symbol = "??";
+ }
+ } else {
+ symbol = library_metadata->symbolicate(ptr, offset);
+ }
+
+ event.frames.append({ symbol, ptr, offset });
+ }
+
+ if (event.frames.size() < 2)
+ continue;
+
+ FlatPtr innermost_frame_address = event.frames.at(1).address;
+ event.in_kernel = innermost_frame_address >= 0xc0000000;
+
+ events.append(move(event));
+ }
+
+ return adopt_own(*new Profile(executable_path, move(events), move(library_metadata)));
+}
+
+void ProfileNode::sort_children()
+{
+ sort_profile_nodes(m_children);
+}
+
+void Profile::set_timestamp_filter_range(u64 start, u64 end)
+{
+ if (m_has_timestamp_filter_range && m_timestamp_filter_range_start == start && m_timestamp_filter_range_end == end)
+ return;
+ m_has_timestamp_filter_range = true;
+
+ m_timestamp_filter_range_start = min(start, end);
+ m_timestamp_filter_range_end = max(start, end);
+
+ rebuild_tree();
+}
+
+void Profile::clear_timestamp_filter_range()
+{
+ if (!m_has_timestamp_filter_range)
+ return;
+ m_has_timestamp_filter_range = false;
+ rebuild_tree();
+}
+
+void Profile::set_inverted(bool inverted)
+{
+ if (m_inverted == inverted)
+ return;
+ m_inverted = inverted;
+ rebuild_tree();
+}
+
+void Profile::set_show_top_functions(bool show)
+{
+ if (m_show_top_functions == show)
+ return;
+ m_show_top_functions = show;
+ rebuild_tree();
+}
+
+void Profile::set_show_percentages(bool show_percentages)
+{
+ if (m_show_percentages == show_percentages)
+ return;
+ m_show_percentages = show_percentages;
+}
+
+void Profile::set_disassembly_index(const GUI::ModelIndex& index)
+{
+ if (m_disassembly_index == index)
+ return;
+ m_disassembly_index = index;
+ auto* node = static_cast<ProfileNode*>(index.internal_data());
+ m_disassembly_model = DisassemblyModel::create(*this, *node);
+}
+
+GUI::Model* Profile::disassembly_model()
+{
+ return m_disassembly_model;
+}
+
+Profile::LibraryMetadata::LibraryMetadata(JsonArray regions)
+ : m_regions(move(regions))
+{
+ for (auto& region_value : m_regions.values()) {
+ auto& region = region_value.as_object();
+ auto base = region.get("base").as_u32();
+ auto size = region.get("size").as_u32();
+ auto name = region.get("name").as_string();
+
+ String path;
+ if (name.contains("Loader.so"))
+ path = "Loader.so";
+ else if (!name.contains(":"))
+ continue;
+ else
+ path = name.substring(0, name.view().find_first_of(":").value());
+
+ if (name.contains(".so"))
+ path = String::formatted("/usr/lib/{}", path);
+
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error()) {
+ m_libraries.set(name, {});
+ continue;
+ }
+ auto elf = ELF::Image(file_or_error.value()->bytes());
+ if (!elf.is_valid())
+ continue;
+ auto library = make<Library>(base, size, name, file_or_error.release_value(), move(elf));
+ m_libraries.set(name, move(library));
+ }
+}
+
+const Profile::LibraryMetadata::Library* Profile::LibraryMetadata::library_containing(FlatPtr ptr) const
+{
+ for (auto& it : m_libraries) {
+ if (!it.value)
+ continue;
+ auto& library = *it.value;
+ if (ptr >= library.base && ptr < (library.base + library.size))
+ return &library;
+ }
+ return nullptr;
+}
+
+String Profile::LibraryMetadata::symbolicate(FlatPtr ptr, u32& offset) const
+{
+ if (auto* library = library_containing(ptr))
+ return String::formatted("[{}] {}", library->name, library->elf.symbolicate(ptr - library->base, &offset));
+ return "??";
+}
diff --git a/Userland/DevTools/Profiler/Profile.h b/Userland/DevTools/Profiler/Profile.h
new file mode 100644
index 0000000000..0acf3e917f
--- /dev/null
+++ b/Userland/DevTools/Profiler/Profile.h
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Bitmap.h>
+#include <AK/JsonArray.h>
+#include <AK/JsonObject.h>
+#include <AK/JsonValue.h>
+#include <AK/MappedFile.h>
+#include <AK/NonnullRefPtrVector.h>
+#include <AK/OwnPtr.h>
+#include <AK/Result.h>
+#include <LibELF/Image.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/ModelIndex.h>
+
+class ProfileModel;
+class DisassemblyModel;
+
+class ProfileNode : public RefCounted<ProfileNode> {
+public:
+ static NonnullRefPtr<ProfileNode> create(const String& symbol, u32 address, u32 offset, u64 timestamp)
+ {
+ return adopt(*new ProfileNode(symbol, address, offset, timestamp));
+ }
+
+ // These functions are only relevant for root nodes
+ void will_track_seen_events(size_t profile_event_count)
+ {
+ if (m_seen_events.size() != profile_event_count)
+ m_seen_events = Bitmap::create(profile_event_count, false);
+ }
+ bool has_seen_event(size_t event_index) const { return m_seen_events.get(event_index); }
+ void did_see_event(size_t event_index) { m_seen_events.set(event_index, true); }
+
+ const String& symbol() const { return m_symbol; }
+ u32 address() const { return m_address; }
+ u32 offset() const { return m_offset; }
+ u64 timestamp() const { return m_timestamp; }
+
+ u32 event_count() const { return m_event_count; }
+ u32 self_count() const { return m_self_count; }
+
+ int child_count() const { return m_children.size(); }
+ const Vector<NonnullRefPtr<ProfileNode>>& children() const { return m_children; }
+
+ void add_child(ProfileNode& child)
+ {
+ if (child.m_parent == this)
+ return;
+ ASSERT(!child.m_parent);
+ child.m_parent = this;
+ m_children.append(child);
+ }
+
+ ProfileNode& find_or_create_child(const String& symbol, u32 address, u32 offset, u64 timestamp)
+ {
+ for (size_t i = 0; i < m_children.size(); ++i) {
+ auto& child = m_children[i];
+ if (child->symbol() == symbol) {
+ return child;
+ }
+ }
+ auto new_child = ProfileNode::create(symbol, address, offset, timestamp);
+ add_child(new_child);
+ return new_child;
+ };
+
+ ProfileNode* parent() { return m_parent; }
+ const ProfileNode* parent() const { return m_parent; }
+
+ void increment_event_count() { ++m_event_count; }
+ void increment_self_count() { ++m_self_count; }
+
+ void sort_children();
+
+ const HashMap<FlatPtr, size_t>& events_per_address() const { return m_events_per_address; }
+ void add_event_address(FlatPtr address)
+ {
+ auto it = m_events_per_address.find(address);
+ if (it == m_events_per_address.end())
+ m_events_per_address.set(address, 1);
+ else
+ m_events_per_address.set(address, it->value + 1);
+ }
+
+private:
+ explicit ProfileNode(const String& symbol, u32 address, u32 offset, u64 timestamp)
+ : m_symbol(symbol)
+ , m_address(address)
+ , m_offset(offset)
+ , m_timestamp(timestamp)
+ {
+ }
+
+ ProfileNode* m_parent { nullptr };
+ String m_symbol;
+ u32 m_address { 0 };
+ u32 m_offset { 0 };
+ u32 m_event_count { 0 };
+ u32 m_self_count { 0 };
+ u64 m_timestamp { 0 };
+ Vector<NonnullRefPtr<ProfileNode>> m_children;
+ HashMap<FlatPtr, size_t> m_events_per_address;
+ Bitmap m_seen_events;
+};
+
+class Profile {
+public:
+ static Result<NonnullOwnPtr<Profile>, String> load_from_perfcore_file(const StringView& path);
+ ~Profile();
+
+ GUI::Model& model();
+ GUI::Model* disassembly_model();
+
+ void set_disassembly_index(const GUI::ModelIndex&);
+
+ const Vector<NonnullRefPtr<ProfileNode>>& roots() const { return m_roots; }
+
+ struct Frame {
+ String symbol;
+ u32 address { 0 };
+ u32 offset { 0 };
+ };
+
+ struct Event {
+ u64 timestamp { 0 };
+ String type;
+ FlatPtr ptr { 0 };
+ size_t size { 0 };
+ bool in_kernel { false };
+ Vector<Frame> frames;
+ };
+
+ u32 filtered_event_count() const { return m_filtered_event_count; }
+
+ const Vector<Event>& events() const { return m_events; }
+
+ u64 length_in_ms() const { return m_last_timestamp - m_first_timestamp; }
+ u64 first_timestamp() const { return m_first_timestamp; }
+ u64 last_timestamp() const { return m_last_timestamp; }
+ u32 deepest_stack_depth() const { return m_deepest_stack_depth; }
+
+ void set_timestamp_filter_range(u64 start, u64 end);
+ void clear_timestamp_filter_range();
+ bool has_timestamp_filter_range() const { return m_has_timestamp_filter_range; }
+
+ bool is_inverted() const { return m_inverted; }
+ void set_inverted(bool);
+
+ void set_show_top_functions(bool);
+
+ bool show_percentages() const { return m_show_percentages; }
+ void set_show_percentages(bool);
+
+ const String& executable_path() const { return m_executable_path; }
+
+ class LibraryMetadata {
+ public:
+ LibraryMetadata(JsonArray regions);
+
+ String symbolicate(FlatPtr ptr, u32& offset) const;
+
+ struct Library {
+ FlatPtr base;
+ size_t size;
+ String name;
+ NonnullRefPtr<MappedFile> file;
+ ELF::Image elf;
+ };
+
+ const Library* library_containing(FlatPtr) const;
+
+ private:
+ mutable HashMap<String, OwnPtr<Library>> m_libraries;
+ JsonArray m_regions;
+ };
+
+ const LibraryMetadata& libraries() const { return *m_library_metadata; }
+
+private:
+ Profile(String executable_path, Vector<Event>, NonnullOwnPtr<LibraryMetadata>);
+
+ void rebuild_tree();
+
+ String m_executable_path;
+
+ RefPtr<ProfileModel> m_model;
+ RefPtr<DisassemblyModel> m_disassembly_model;
+
+ GUI::ModelIndex m_disassembly_index;
+
+ Vector<NonnullRefPtr<ProfileNode>> m_roots;
+ u32 m_filtered_event_count { 0 };
+ u64 m_first_timestamp { 0 };
+ u64 m_last_timestamp { 0 };
+
+ Vector<Event> m_events;
+
+ NonnullOwnPtr<LibraryMetadata> m_library_metadata;
+
+ bool m_has_timestamp_filter_range { false };
+ u64 m_timestamp_filter_range_start { 0 };
+ u64 m_timestamp_filter_range_end { 0 };
+
+ u32 m_deepest_stack_depth { 0 };
+ bool m_inverted { false };
+ bool m_show_top_functions { false };
+ bool m_show_percentages { false };
+};
diff --git a/Userland/DevTools/Profiler/ProfileModel.cpp b/Userland/DevTools/Profiler/ProfileModel.cpp
new file mode 100644
index 0000000000..caf1819446
--- /dev/null
+++ b/Userland/DevTools/Profiler/ProfileModel.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ProfileModel.h"
+#include "Profile.h"
+#include <AK/StringBuilder.h>
+#include <ctype.h>
+#include <stdio.h>
+
+ProfileModel::ProfileModel(Profile& profile)
+ : m_profile(profile)
+{
+ m_user_frame_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png"));
+ m_kernel_frame_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object-red.png"));
+}
+
+ProfileModel::~ProfileModel()
+{
+}
+
+GUI::ModelIndex ProfileModel::index(int row, int column, const GUI::ModelIndex& parent) const
+{
+ if (!parent.is_valid()) {
+ if (m_profile.roots().is_empty())
+ return {};
+ return create_index(row, column, m_profile.roots().at(row).ptr());
+ }
+ auto& remote_parent = *static_cast<ProfileNode*>(parent.internal_data());
+ return create_index(row, column, remote_parent.children().at(row).ptr());
+}
+
+GUI::ModelIndex ProfileModel::parent_index(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+ auto& node = *static_cast<ProfileNode*>(index.internal_data());
+ if (!node.parent())
+ return {};
+
+ // NOTE: If the parent has no parent, it's a root, so we have to look among the roots.
+ if (!node.parent()->parent()) {
+ for (size_t row = 0; row < m_profile.roots().size(); ++row) {
+ if (m_profile.roots()[row].ptr() == node.parent()) {
+ return create_index(row, index.column(), node.parent());
+ }
+ }
+ ASSERT_NOT_REACHED();
+ return {};
+ }
+
+ for (size_t row = 0; row < node.parent()->parent()->children().size(); ++row) {
+ if (node.parent()->parent()->children()[row].ptr() == node.parent())
+ return create_index(row, index.column(), node.parent());
+ }
+
+ ASSERT_NOT_REACHED();
+ return {};
+}
+
+int ProfileModel::row_count(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return m_profile.roots().size();
+ auto& node = *static_cast<ProfileNode*>(index.internal_data());
+ return node.children().size();
+}
+
+int ProfileModel::column_count(const GUI::ModelIndex&) const
+{
+ return Column::__Count;
+}
+
+String ProfileModel::column_name(int column) const
+{
+ switch (column) {
+ case Column::SampleCount:
+ return m_profile.show_percentages() ? "% Samples" : "# Samples";
+ case Column::SelfCount:
+ return m_profile.show_percentages() ? "% Self" : "# Self";
+ case Column::StackFrame:
+ return "Stack Frame";
+ default:
+ ASSERT_NOT_REACHED();
+ return {};
+ }
+}
+
+GUI::Variant ProfileModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
+{
+ auto* node = static_cast<ProfileNode*>(index.internal_data());
+ if (role == GUI::ModelRole::TextAlignment) {
+ if (index.column() == Column::SampleCount || index.column() == Column::SelfCount)
+ return Gfx::TextAlignment::CenterRight;
+ }
+ if (role == GUI::ModelRole::Icon) {
+ if (index.column() == Column::StackFrame) {
+ if (node->address() >= 0xc0000000)
+ return m_kernel_frame_icon;
+ return m_user_frame_icon;
+ }
+ return {};
+ }
+ if (role == GUI::ModelRole::Display) {
+ if (index.column() == Column::SampleCount) {
+ if (m_profile.show_percentages())
+ return ((float)node->event_count() / (float)m_profile.filtered_event_count()) * 100.0f;
+ return node->event_count();
+ }
+ if (index.column() == Column::SelfCount) {
+ if (m_profile.show_percentages())
+ return ((float)node->self_count() / (float)m_profile.filtered_event_count()) * 100.0f;
+ return node->self_count();
+ }
+ if (index.column() == Column::StackFrame)
+ return node->symbol();
+ return {};
+ }
+ return {};
+}
+
+void ProfileModel::update()
+{
+ did_update(Model::InvalidateAllIndexes);
+}
diff --git a/Userland/DevTools/Profiler/ProfileModel.h b/Userland/DevTools/Profiler/ProfileModel.h
new file mode 100644
index 0000000000..9b61dd5137
--- /dev/null
+++ b/Userland/DevTools/Profiler/ProfileModel.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Model.h>
+
+class Profile;
+
+class ProfileModel final : public GUI::Model {
+public:
+ static NonnullRefPtr<ProfileModel> create(Profile& profile)
+ {
+ return adopt(*new ProfileModel(profile));
+ }
+
+ enum Column {
+ SampleCount,
+ SelfCount,
+ StackFrame,
+ __Count
+ };
+
+ virtual ~ProfileModel() override;
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual String column_name(int) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
+ virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override;
+ virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override;
+ virtual void update() override;
+ virtual int tree_column() const override { return Column::StackFrame; }
+
+private:
+ explicit ProfileModel(Profile&);
+
+ Profile& m_profile;
+
+ GUI::Icon m_user_frame_icon;
+ GUI::Icon m_kernel_frame_icon;
+};
diff --git a/Userland/DevTools/Profiler/ProfileTimelineWidget.cpp b/Userland/DevTools/Profiler/ProfileTimelineWidget.cpp
new file mode 100644
index 0000000000..7d4552d300
--- /dev/null
+++ b/Userland/DevTools/Profiler/ProfileTimelineWidget.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ProfileTimelineWidget.h"
+#include "Profile.h"
+#include <LibGUI/Painter.h>
+
+ProfileTimelineWidget::ProfileTimelineWidget(Profile& profile)
+ : m_profile(profile)
+{
+ set_fill_with_background_color(true);
+ set_fixed_height(80);
+}
+
+ProfileTimelineWidget::~ProfileTimelineWidget()
+{
+}
+
+void ProfileTimelineWidget::paint_event(GUI::PaintEvent& event)
+{
+ GUI::Frame::paint_event(event);
+
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ float column_width = (float)frame_inner_rect().width() / (float)m_profile.length_in_ms();
+ float frame_height = (float)frame_inner_rect().height() / (float)m_profile.deepest_stack_depth();
+
+ for (auto& event : m_profile.events()) {
+ u64 t = event.timestamp - m_profile.first_timestamp();
+ int x = (int)((float)t * column_width);
+ int cw = max(1, (int)column_width);
+
+ int column_height = frame_inner_rect().height() - (int)((float)event.frames.size() * frame_height);
+
+ bool in_kernel = event.in_kernel;
+ Color color = in_kernel ? Color::from_rgb(0xc25e5a) : Color::from_rgb(0x5a65c2);
+ for (int i = 0; i < cw; ++i)
+ painter.draw_line({ x + i, frame_thickness() + column_height }, { x + i, height() - frame_thickness() * 2 }, color);
+ }
+
+ u64 normalized_start_time = min(m_select_start_time, m_select_end_time);
+ u64 normalized_end_time = max(m_select_start_time, m_select_end_time);
+
+ int select_start_x = (int)((float)(normalized_start_time - m_profile.first_timestamp()) * column_width);
+ int select_end_x = (int)((float)(normalized_end_time - m_profile.first_timestamp()) * column_width);
+ painter.fill_rect({ select_start_x, frame_thickness(), select_end_x - select_start_x, height() - frame_thickness() * 2 }, Color(0, 0, 0, 60));
+}
+
+u64 ProfileTimelineWidget::timestamp_at_x(int x) const
+{
+ float column_width = (float)frame_inner_rect().width() / (float)m_profile.length_in_ms();
+ float ms_into_profile = (float)x / column_width;
+ return m_profile.first_timestamp() + (u64)ms_into_profile;
+}
+
+void ProfileTimelineWidget::mousedown_event(GUI::MouseEvent& event)
+{
+ if (event.button() != GUI::MouseButton::Left)
+ return;
+
+ m_selecting = true;
+ m_select_start_time = timestamp_at_x(event.x());
+ m_select_end_time = m_select_start_time;
+ m_profile.set_timestamp_filter_range(m_select_start_time, m_select_end_time);
+ update();
+}
+
+void ProfileTimelineWidget::mousemove_event(GUI::MouseEvent& event)
+{
+ if (!m_selecting)
+ return;
+
+ m_select_end_time = timestamp_at_x(event.x());
+ m_profile.set_timestamp_filter_range(m_select_start_time, m_select_end_time);
+ update();
+}
+
+void ProfileTimelineWidget::mouseup_event(GUI::MouseEvent& event)
+{
+ if (event.button() != GUI::MouseButton::Left)
+ return;
+
+ m_selecting = false;
+ if (m_select_start_time == m_select_end_time)
+ m_profile.clear_timestamp_filter_range();
+}
diff --git a/Userland/DevTools/Profiler/ProfileTimelineWidget.h b/Userland/DevTools/Profiler/ProfileTimelineWidget.h
new file mode 100644
index 0000000000..056e07769e
--- /dev/null
+++ b/Userland/DevTools/Profiler/ProfileTimelineWidget.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Frame.h>
+
+class Profile;
+
+class ProfileTimelineWidget final : public GUI::Frame {
+ C_OBJECT(ProfileTimelineWidget)
+public:
+ virtual ~ProfileTimelineWidget() override;
+
+private:
+ virtual void paint_event(GUI::PaintEvent&) override;
+ virtual void mousedown_event(GUI::MouseEvent&) override;
+ virtual void mousemove_event(GUI::MouseEvent&) override;
+ virtual void mouseup_event(GUI::MouseEvent&) override;
+
+ explicit ProfileTimelineWidget(Profile&);
+
+ u64 timestamp_at_x(int x) const;
+
+ Profile& m_profile;
+
+ bool m_selecting { false };
+ u64 m_select_start_time { 0 };
+ u64 m_select_end_time { 0 };
+};
diff --git a/Userland/DevTools/Profiler/main.cpp b/Userland/DevTools/Profiler/main.cpp
new file mode 100644
index 0000000000..5f801936c0
--- /dev/null
+++ b/Userland/DevTools/Profiler/main.cpp
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Profile.h"
+#include "ProfileTimelineWidget.h"
+#include <LibCore/ArgsParser.h>
+#include <LibCore/ElapsedTimer.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/ProcessStatisticsReader.h>
+#include <LibCore/Timer.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/Desktop.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/Menu.h>
+#include <LibGUI/MenuBar.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGUI/Model.h>
+#include <LibGUI/ProcessChooser.h>
+#include <LibGUI/Splitter.h>
+#include <LibGUI/TableView.h>
+#include <LibGUI/TreeView.h>
+#include <LibGUI/Window.h>
+#include <serenity.h>
+#include <stdio.h>
+#include <string.h>
+
+static bool generate_profile(pid_t& pid);
+
+int main(int argc, char** argv)
+{
+ Core::ArgsParser args_parser;
+ int pid = 0;
+ args_parser.add_option(pid, "PID to profile", "pid", 'p', "PID");
+ args_parser.parse(argc, argv, false);
+
+ auto app = GUI::Application::construct(argc, argv);
+ auto app_icon = GUI::Icon::default_icon("app-profiler");
+
+ String path;
+ if (argc != 2) {
+ if (!generate_profile(pid))
+ return 0;
+ path = String::formatted("/proc/{}/perf_events", pid);
+ } else {
+ path = argv[1];
+ }
+
+ auto profile_or_error = Profile::load_from_perfcore_file(path);
+ if (profile_or_error.is_error()) {
+ GUI::MessageBox::show(nullptr, profile_or_error.error(), "Profiler", GUI::MessageBox::Type::Error);
+ return 0;
+ }
+
+ auto& profile = profile_or_error.value();
+
+ auto window = GUI::Window::construct();
+ window->set_title("Profiler");
+ window->set_icon(app_icon.bitmap_for_size(16));
+ window->resize(800, 600);
+
+ auto& main_widget = window->set_main_widget<GUI::Widget>();
+ main_widget.set_fill_with_background_color(true);
+ main_widget.set_layout<GUI::VerticalBoxLayout>();
+
+ main_widget.add<ProfileTimelineWidget>(*profile);
+
+ auto& bottom_splitter = main_widget.add<GUI::VerticalSplitter>();
+
+ auto& tree_view = bottom_splitter.add<GUI::TreeView>();
+ tree_view.set_should_fill_selected_rows(true);
+ tree_view.set_column_headers_visible(true);
+ tree_view.set_model(profile->model());
+
+ auto& disassembly_view = bottom_splitter.add<GUI::TableView>();
+
+ tree_view.on_selection = [&](auto& index) {
+ profile->set_disassembly_index(index);
+ disassembly_view.set_model(profile->disassembly_model());
+ };
+
+ auto menubar = GUI::MenuBar::construct();
+ auto& app_menu = menubar->add_menu("Profiler");
+ app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); }));
+
+ auto& view_menu = menubar->add_menu("View");
+
+ auto update_window_title = [&](auto title, bool is_checked) {
+ StringBuilder name;
+ name.append("Profiler");
+ if (is_checked) {
+ name.append(" - ");
+ name.append(title);
+ }
+ window->set_title(name.to_string());
+ };
+
+ auto invert_action = GUI::Action::create_checkable("Invert tree", { Mod_Ctrl, Key_I }, [&](auto& action) {
+ profile->set_inverted(action.is_checked());
+ update_window_title("Invert tree", action.is_checked());
+ });
+ invert_action->set_checked(false);
+ view_menu.add_action(invert_action);
+
+ auto top_functions_action = GUI::Action::create_checkable("Top functions", { Mod_Ctrl, Key_T }, [&](auto& action) {
+ profile->set_show_top_functions(action.is_checked());
+ update_window_title("Top functions", action.is_checked());
+ });
+ top_functions_action->set_checked(false);
+ view_menu.add_action(top_functions_action);
+
+ auto percent_action = GUI::Action::create_checkable("Show percentages", { Mod_Ctrl, Key_P }, [&](auto& action) {
+ profile->set_show_percentages(action.is_checked());
+ tree_view.update();
+ disassembly_view.update();
+ update_window_title("Show percentages", action.is_checked());
+ });
+ percent_action->set_checked(false);
+ view_menu.add_action(percent_action);
+
+ auto& help_menu = menubar->add_menu("Help");
+ help_menu.add_action(GUI::CommonActions::make_about_action("Profiler", app_icon, window));
+
+ app->set_menubar(move(menubar));
+
+ window->show();
+ return app->exec();
+}
+
+static bool prompt_to_stop_profiling(pid_t pid, const String& process_name)
+{
+ auto window = GUI::Window::construct();
+ window->set_title(String::formatted("Profiling {}({})", process_name, pid));
+ window->resize(240, 100);
+ window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-profiler.png"));
+ window->center_on_screen();
+
+ auto& widget = window->set_main_widget<GUI::Widget>();
+ widget.set_fill_with_background_color(true);
+ auto& layout = widget.set_layout<GUI::VerticalBoxLayout>();
+ layout.set_margins(GUI::Margins(0, 0, 0, 16));
+
+ auto& timer_label = widget.add<GUI::Label>("...");
+ Core::ElapsedTimer clock;
+ clock.start();
+ auto update_timer = Core::Timer::construct(100, [&] {
+ timer_label.set_text(String::format("%.1f seconds", (float)clock.elapsed() / 1000.0f));
+ });
+
+ auto& stop_button = widget.add<GUI::Button>("Stop");
+ stop_button.set_fixed_size(140, 22);
+ stop_button.on_click = [&](auto) {
+ GUI::Application::the()->quit();
+ };
+
+ window->show();
+ return GUI::Application::the()->exec() == 0;
+}
+
+bool generate_profile(pid_t& pid)
+{
+ if (!pid) {
+ auto process_chooser = GUI::ProcessChooser::construct("Profiler", "Profile", Gfx::Bitmap::load_from_file("/res/icons/16x16/app-profiler.png"));
+ if (process_chooser->exec() == GUI::Dialog::ExecCancel)
+ return false;
+ pid = process_chooser->pid();
+ }
+
+ String process_name;
+
+ auto all_processes = Core::ProcessStatisticsReader::get_all();
+ if (all_processes.has_value()) {
+ if (auto it = all_processes.value().find(pid); it != all_processes.value().end())
+ process_name = it->value.name;
+ else
+ process_name = "(unknown)";
+ } else {
+ process_name = "(unknown)";
+ }
+
+ if (profiling_enable(pid) < 0) {
+ int saved_errno = errno;
+ GUI::MessageBox::show(nullptr, String::formatted("Unable to profile process {}({}): {}", process_name, pid, strerror(saved_errno)), "Profiler", GUI::MessageBox::Type::Error);
+ return false;
+ }
+
+ if (!prompt_to_stop_profiling(pid, process_name))
+ return false;
+
+ if (profiling_disable(pid) < 0) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/Userland/DevTools/UserspaceEmulator/CMakeLists.txt b/Userland/DevTools/UserspaceEmulator/CMakeLists.txt
new file mode 100644
index 0000000000..dd2a2b37ad
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(SOURCES
+ Emulator.cpp
+ MallocTracer.cpp
+ MmapRegion.cpp
+ Region.cpp
+ SharedBufferRegion.cpp
+ SimpleRegion.cpp
+ SoftCPU.cpp
+ SoftMMU.cpp
+ main.cpp
+)
+
+serenity_bin(UserspaceEmulator)
+target_link_libraries(UserspaceEmulator LibX86 LibDebug LibCore LibPthread)
diff --git a/Userland/DevTools/UserspaceEmulator/Emulator.cpp b/Userland/DevTools/UserspaceEmulator/Emulator.cpp
new file mode 100644
index 0000000000..c463d24618
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/Emulator.cpp
@@ -0,0 +1,1816 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Emulator.h"
+#include "MmapRegion.h"
+#include "SharedBufferRegion.h"
+#include "SimpleRegion.h"
+#include "SoftCPU.h"
+#include <AK/Format.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <Kernel/API/Syscall.h>
+#include <LibELF/AuxiliaryVector.h>
+#include <LibELF/Image.h>
+#include <LibELF/Validation.h>
+#include <LibPthread/pthread.h>
+#include <LibX86/ELFSymbolProvider.h>
+#include <fcntl.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <sched.h>
+#include <serenity.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC optimize("O3")
+#endif
+
+// #define DEBUG_SPAM
+
+namespace UserspaceEmulator {
+
+static constexpr u32 stack_location = 0x10000000;
+static constexpr size_t stack_size = 64 * KiB;
+
+static Emulator* s_the;
+
+Emulator& Emulator::the()
+{
+ ASSERT(s_the);
+ return *s_the;
+}
+
+Emulator::Emulator(const String& executable_path, const Vector<String>& arguments, const Vector<String>& environment)
+ : m_executable_path(executable_path)
+ , m_arguments(arguments)
+ , m_environment(environment)
+ , m_mmu(*this)
+ , m_cpu(*this)
+{
+ m_malloc_tracer = make<MallocTracer>(*this);
+ ASSERT(!s_the);
+ s_the = this;
+ // setup_stack(arguments, environment);
+ register_signal_handlers();
+ setup_signal_trampoline();
+}
+
+Vector<ELF::AuxiliaryValue> Emulator::generate_auxiliary_vector(FlatPtr load_base, FlatPtr entry_eip, String executable_path, int executable_fd) const
+{
+ // FIXME: This is not fully compatible with the auxiliary vector the kernel generates, this is just the bare
+ // minimum to get the loader going.
+ Vector<ELF::AuxiliaryValue> auxv;
+ // PHDR/EXECFD
+ // PH*
+ auxv.append({ ELF::AuxiliaryValue::PageSize, PAGE_SIZE });
+ auxv.append({ ELF::AuxiliaryValue::BaseAddress, (void*)load_base });
+
+ auxv.append({ ELF::AuxiliaryValue::Entry, (void*)entry_eip });
+
+ // FIXME: Don't hard code this? We might support other platforms later.. (e.g. x86_64)
+ auxv.append({ ELF::AuxiliaryValue::Platform, "i386" });
+
+ auxv.append({ ELF::AuxiliaryValue::ExecFilename, executable_path });
+
+ auxv.append({ ELF::AuxiliaryValue::ExecFileDescriptor, executable_fd });
+
+ auxv.append({ ELF::AuxiliaryValue::Null, 0L });
+ return auxv;
+}
+
+void Emulator::setup_stack(Vector<ELF::AuxiliaryValue> aux_vector)
+{
+ auto stack_region = make<SimpleRegion>(stack_location, stack_size);
+ stack_region->set_stack(true);
+ m_mmu.add_region(move(stack_region));
+ m_cpu.set_esp(shadow_wrap_as_initialized<u32>(stack_location + stack_size));
+
+ Vector<u32> argv_entries;
+
+ for (auto& argument : m_arguments) {
+ m_cpu.push_string(argument.characters());
+ argv_entries.append(m_cpu.esp().value());
+ }
+
+ Vector<u32> env_entries;
+
+ for (auto& variable : m_environment) {
+ m_cpu.push_string(variable.characters());
+ env_entries.append(m_cpu.esp().value());
+ }
+
+ for (auto& auxv : aux_vector) {
+ if (!auxv.optional_string.is_empty()) {
+ m_cpu.push_string(auxv.optional_string.characters());
+ auxv.auxv.a_un.a_ptr = (void*)m_cpu.esp().value();
+ }
+ }
+
+ for (ssize_t i = aux_vector.size() - 1; i >= 0; --i) {
+ auto& value = aux_vector[i].auxv;
+ m_cpu.push_buffer((const u8*)&value, sizeof(value));
+ }
+
+ m_cpu.push32(shadow_wrap_as_initialized<u32>(0)); // char** envp = { envv_entries..., nullptr }
+ for (ssize_t i = env_entries.size() - 1; i >= 0; --i)
+ m_cpu.push32(shadow_wrap_as_initialized(env_entries[i]));
+ u32 envp = m_cpu.esp().value();
+
+ m_cpu.push32(shadow_wrap_as_initialized<u32>(0)); // char** argv = { argv_entries..., nullptr }
+ for (ssize_t i = argv_entries.size() - 1; i >= 0; --i)
+ m_cpu.push32(shadow_wrap_as_initialized(argv_entries[i]));
+ u32 argv = m_cpu.esp().value();
+
+ m_cpu.push32(shadow_wrap_as_initialized<u32>(0)); // (alignment)
+
+ u32 argc = argv_entries.size();
+ m_cpu.push32(shadow_wrap_as_initialized(envp));
+ m_cpu.push32(shadow_wrap_as_initialized(argv));
+ m_cpu.push32(shadow_wrap_as_initialized(argc));
+ m_cpu.push32(shadow_wrap_as_initialized<u32>(0)); // (alignment)
+}
+
+bool Emulator::load_elf()
+{
+ auto file_or_error = MappedFile::map(m_executable_path);
+ if (file_or_error.is_error()) {
+ reportln("Unable to map {}: {}", m_executable_path, file_or_error.error());
+ return false;
+ }
+
+ auto elf_image_data = file_or_error.value()->bytes();
+ ELF::Image executable_elf(elf_image_data);
+
+ if (!executable_elf.is_dynamic()) {
+ // FIXME: Support static objects
+ ASSERT_NOT_REACHED();
+ }
+
+ String interpreter_path;
+ if (!ELF::validate_program_headers(*(const Elf32_Ehdr*)elf_image_data.data(), elf_image_data.size(), (const u8*)elf_image_data.data(), elf_image_data.size(), &interpreter_path)) {
+ reportln("failed to validate ELF file");
+ return false;
+ }
+
+ ASSERT(!interpreter_path.is_null());
+ dbgln("interpreter: {}", interpreter_path);
+
+ auto interpreter_file_or_error = MappedFile::map(interpreter_path);
+ ASSERT(!interpreter_file_or_error.is_error());
+ auto interpreter_image_data = interpreter_file_or_error.value()->bytes();
+ ELF::Image interpreter_image(interpreter_image_data);
+
+ constexpr FlatPtr interpreter_load_offset = 0x08000000;
+ interpreter_image.for_each_program_header([&](const ELF::Image::ProgramHeader& program_header) {
+ // Loader is not allowed to have its own TLS regions
+ ASSERT(program_header.type() != PT_TLS);
+
+ if (program_header.type() == PT_LOAD) {
+ auto region = make<SimpleRegion>(program_header.vaddr().offset(interpreter_load_offset).get(), program_header.size_in_memory());
+ if (program_header.is_executable() && !program_header.is_writable())
+ region->set_text(true);
+ memcpy(region->data(), program_header.raw_data(), program_header.size_in_image());
+ memset(region->shadow_data(), 0x01, program_header.size_in_memory());
+ if (program_header.is_executable()) {
+ m_loader_text_base = region->base();
+ m_loader_text_size = region->size();
+ }
+ mmu().add_region(move(region));
+ return IterationDecision::Continue;
+ }
+
+ return IterationDecision::Continue;
+ });
+
+ auto entry_point = interpreter_image.entry().offset(interpreter_load_offset).get();
+ m_cpu.set_eip(entry_point);
+
+ // executable_fd will be used by the loader
+ int executable_fd = open(m_executable_path.characters(), O_RDONLY);
+ if (executable_fd < 0)
+ return false;
+
+ auto aux_vector = generate_auxiliary_vector(interpreter_load_offset, entry_point, m_executable_path, executable_fd);
+ setup_stack(move(aux_vector));
+
+ return true;
+}
+
+int Emulator::exec()
+{
+ // X86::ELFSymbolProvider symbol_provider(*m_elf);
+ X86::ELFSymbolProvider* symbol_provider = nullptr;
+
+ bool trace = false;
+
+ while (!m_shutdown) {
+ m_cpu.save_base_eip();
+
+ auto insn = X86::Instruction::from_stream(m_cpu, true, true);
+
+ if (trace)
+ outln("{:p} \033[33;1m{}\033[0m", m_cpu.base_eip(), insn.to_string(m_cpu.base_eip(), symbol_provider));
+
+ (m_cpu.*insn.handler())(insn);
+
+ if (trace)
+ m_cpu.dump();
+
+ if (m_pending_signals)
+ dispatch_one_pending_signal();
+ }
+
+ if (auto* tracer = malloc_tracer())
+ tracer->dump_leak_report();
+
+ return m_exit_status;
+}
+
+Vector<FlatPtr> Emulator::raw_backtrace()
+{
+ Vector<FlatPtr, 128> backtrace;
+ backtrace.append(m_cpu.base_eip());
+
+ // FIXME: Maybe do something if the backtrace has uninitialized data in the frame chain.
+
+ u32 frame_ptr = m_cpu.ebp().value();
+ while (frame_ptr) {
+ u32 ret_ptr = m_mmu.read32({ 0x23, frame_ptr + 4 }).value();
+ if (!ret_ptr)
+ break;
+ backtrace.append(ret_ptr);
+ frame_ptr = m_mmu.read32({ 0x23, frame_ptr }).value();
+ }
+ return backtrace;
+}
+
+const MmapRegion* Emulator::find_text_region(FlatPtr address)
+{
+ const MmapRegion* matching_region = nullptr;
+ mmu().for_each_region([&](auto& region) {
+ if (!is<MmapRegion>(region))
+ return IterationDecision::Continue;
+ const auto& mmap_region = static_cast<const MmapRegion&>(region);
+ if (!(mmap_region.is_executable() && address >= mmap_region.base() && address < mmap_region.base() + mmap_region.size()))
+ return IterationDecision::Continue;
+ matching_region = &mmap_region;
+ return IterationDecision::Break;
+ });
+ return matching_region;
+}
+
+String Emulator::create_backtrace_line(FlatPtr address)
+{
+ String minimal = String::format("=={%d}== %p", getpid(), (void*)address);
+ const auto* region = find_text_region(address);
+ if (!region)
+ return minimal;
+ auto separator_index = region->name().index_of(":");
+ if (!separator_index.has_value())
+ return minimal;
+
+ String lib_name = region->name().substring(0, separator_index.value());
+ String lib_path = lib_name;
+ if (region->name().contains(".so"))
+ lib_path = String::formatted("/usr/lib/{}", lib_path);
+
+ if (!m_dynamic_library_cache.contains(lib_path)) {
+ auto file_or_error = MappedFile::map(lib_path);
+ if (file_or_error.is_error())
+ return minimal;
+
+ auto debug_info = make<Debug::DebugInfo>(make<ELF::Image>(file_or_error.value()->bytes()));
+ m_dynamic_library_cache.set(lib_path, CachedELF { file_or_error.release_value(), move(debug_info) });
+ }
+
+ auto it = m_dynamic_library_cache.find(lib_path);
+ auto& elf = it->value.debug_info->elf();
+ String symbol = elf.symbolicate(address - region->base());
+
+ auto line_without_source_info = String::format("=={%d}== %p [%s]: %s", getpid(), (void*)address, lib_name.characters(), symbol.characters());
+
+ auto source_position = it->value.debug_info->get_source_position(address - region->base());
+ if (source_position.has_value())
+ return String::format("=={%d}== %p [%s]: %s (\033[34;1m%s\033[0m:%zu)", getpid(), (void*)address, lib_name.characters(), symbol.characters(), LexicalPath(source_position.value().file_path).basename().characters(), source_position.value().line_number);
+
+ return line_without_source_info;
+}
+
+void Emulator::dump_backtrace(const Vector<FlatPtr>& backtrace)
+{
+ for (auto& address : backtrace) {
+ reportln("{}", create_backtrace_line(address));
+ }
+}
+
+void Emulator::dump_backtrace()
+{
+ dump_backtrace(raw_backtrace());
+}
+
+u32 Emulator::virt_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3)
+{
+#ifdef DEBUG_SPAM
+ reportln("Syscall: {} ({:x})", Syscall::to_string((Syscall::Function)function), function);
+#endif
+ switch (function) {
+ case SC_chdir:
+ return virt$chdir(arg1, arg2);
+ case SC_dup2:
+ return virt$dup2(arg1, arg2);
+ case SC_get_stack_bounds:
+ return virt$get_stack_bounds(arg1, arg2);
+ case SC_access:
+ return virt$access(arg1, arg2, arg3);
+ case SC_waitid:
+ return virt$waitid(arg1);
+ case SC_getcwd:
+ return virt$getcwd(arg1, arg2);
+ case SC_ttyname:
+ return virt$ttyname(arg1, arg2, arg3);
+ case SC_getpgrp:
+ return virt$getpgrp();
+ case SC_getpgid:
+ return virt$getpgid(arg1);
+ case SC_setpgid:
+ return virt$setpgid(arg1, arg2);
+ case SC_execve:
+ return virt$execve(arg1);
+ case SC_sigaction:
+ return virt$sigaction(arg1, arg2, arg3);
+ case SC_sigreturn:
+ return virt$sigreturn();
+ case SC_stat:
+ return virt$stat(arg1);
+ case SC_realpath:
+ return virt$realpath(arg1);
+ case SC_gethostname:
+ return virt$gethostname(arg1, arg2);
+ case SC_ioctl:
+ return virt$ioctl(arg1, arg2, arg3);
+ case SC_get_dir_entries:
+ return virt$get_dir_entries(arg1, arg2, arg3);
+ case SC_shbuf_create:
+ return virt$shbuf_create(arg1, arg2);
+ case SC_shbuf_allow_pid:
+ return virt$shbuf_allow_pid(arg1, arg2);
+ case SC_shbuf_allow_all:
+ return virt$shbuf_allow_all(arg1);
+ case SC_shbuf_get:
+ return virt$shbuf_get(arg1, arg2);
+ case SC_shbuf_release:
+ return virt$shbuf_release(arg1);
+ case SC_shbuf_seal:
+ return virt$shbuf_seal(arg1);
+ case SC_shbuf_set_volatile:
+ return virt$shbuf_set_volatile(arg1, arg2);
+ case SC_profiling_enable:
+ return virt$profiling_enable(arg1);
+ case SC_profiling_disable:
+ return virt$profiling_disable(arg1);
+ case SC_disown:
+ return virt$disown(arg1);
+ case SC_purge:
+ return virt$purge(arg1);
+ case SC_mmap:
+ return virt$mmap(arg1);
+ case SC_mount:
+ return virt$mount(arg1);
+ case SC_munmap:
+ return virt$munmap(arg1, arg2);
+ case SC_mremap:
+ return virt$mremap(arg1);
+ case SC_gettid:
+ return virt$gettid();
+ case SC_getpid:
+ return virt$getpid();
+ case SC_getsid:
+ return virt$getsid(arg1);
+ case SC_pledge:
+ return virt$pledge(arg1);
+ case SC_unveil:
+ return virt$unveil(arg1);
+ case SC_getuid:
+ return virt$getuid();
+ case SC_geteuid:
+ return virt$geteuid();
+ case SC_getgid:
+ return virt$getgid();
+ case SC_getegid:
+ return virt$getegid();
+ case SC_setuid:
+ return virt$setuid(arg1);
+ case SC_setgid:
+ return virt$setgid(arg2);
+ case SC_close:
+ return virt$close(arg1);
+ case SC_fstat:
+ return virt$fstat(arg1, arg2);
+ case SC_mkdir:
+ return virt$mkdir(arg1, arg2, arg3);
+ case SC_unlink:
+ return virt$unlink(arg1, arg2);
+ case SC_write:
+ return virt$write(arg1, arg2, arg3);
+ case SC_read:
+ return virt$read(arg1, arg2, arg3);
+ case SC_mprotect:
+ return virt$mprotect(arg1, arg2, arg3);
+ case SC_madvise:
+ return virt$madvise(arg1, arg2, arg3);
+ case SC_open:
+ return virt$open(arg1);
+ case SC_pipe:
+ return virt$pipe(arg1, arg2);
+ case SC_fcntl:
+ return virt$fcntl(arg1, arg2, arg3);
+ case SC_getgroups:
+ return virt$getgroups(arg1, arg2);
+ case SC_setgroups:
+ return virt$setgroups(arg1, arg2);
+ case SC_lseek:
+ return virt$lseek(arg1, arg2, arg3);
+ case SC_socket:
+ return virt$socket(arg1, arg2, arg3);
+ case SC_getsockopt:
+ return virt$getsockopt(arg1);
+ case SC_get_process_name:
+ return virt$get_process_name(arg1, arg2);
+ case SC_dbgputstr:
+ return virt$dbgputstr(arg1, arg2);
+ case SC_dbgputch:
+ return virt$dbgputch(arg1);
+ case SC_chmod:
+ return virt$chmod(arg1, arg2, arg3);
+ case SC_fchmod:
+ return virt$fchmod(arg1, arg2);
+ case SC_fchown:
+ return virt$fchown(arg1, arg2, arg3);
+ case SC_accept:
+ return virt$accept(arg1, arg2, arg3);
+ case SC_setsockopt:
+ return virt$setsockopt(arg1);
+ case SC_bind:
+ return virt$bind(arg1, arg2, arg3);
+ case SC_connect:
+ return virt$connect(arg1, arg2, arg3);
+ case SC_listen:
+ return virt$listen(arg1, arg2);
+ case SC_select:
+ return virt$select(arg1);
+ case SC_recvmsg:
+ return virt$recvmsg(arg1, arg2, arg3);
+ case SC_sendmsg:
+ return virt$sendmsg(arg1, arg2, arg3);
+ case SC_kill:
+ return virt$kill(arg1, arg2);
+ case SC_set_mmap_name:
+ return virt$set_mmap_name(arg1);
+ case SC_exit:
+ virt$exit((int)arg1);
+ return 0;
+ case SC_gettimeofday:
+ return virt$gettimeofday(arg1);
+ case SC_clock_gettime:
+ return virt$clock_gettime(arg1, arg2);
+ case SC_clock_settime:
+ return virt$clock_settime(arg1, arg2);
+ case SC_getrandom:
+ return virt$getrandom(arg1, arg2, arg3);
+ case SC_fork:
+ return virt$fork();
+ case SC_sched_getparam:
+ return virt$sched_getparam(arg1, arg2);
+ case SC_sched_setparam:
+ return virt$sched_setparam(arg1, arg2);
+ case SC_set_thread_name:
+ return virt$set_thread_name(arg1, arg2, arg3);
+ case SC_setsid:
+ return virt$setsid();
+ case SC_watch_file:
+ return virt$watch_file(arg1, arg2);
+ case SC_clock_nanosleep:
+ return virt$clock_nanosleep(arg1);
+ case SC_readlink:
+ return virt$readlink(arg1);
+ case SC_ptsname:
+ return virt$ptsname(arg1, arg2, arg3);
+ case SC_allocate_tls:
+ return virt$allocate_tls(arg1);
+ case SC_beep:
+ return virt$beep();
+ case SC_ftruncate:
+ return virt$ftruncate(arg1, arg2);
+ case SC_umask:
+ return virt$umask(arg1);
+ default:
+ reportln("\n=={}== \033[31;1mUnimplemented syscall: {}\033[0m, {:p}", getpid(), Syscall::to_string((Syscall::Function)function), function);
+ dump_backtrace();
+ TODO();
+ }
+}
+
+int Emulator::virt$shbuf_create(int size, FlatPtr buffer)
+{
+ u8* host_data = nullptr;
+ int shbuf_id = syscall(SC_shbuf_create, size, &host_data);
+ if (shbuf_id < 0)
+ return shbuf_id;
+ FlatPtr address = allocate_vm(size, PAGE_SIZE);
+ auto region = SharedBufferRegion::create_with_shbuf_id(address, size, shbuf_id, host_data);
+ m_mmu.add_region(move(region));
+ m_mmu.copy_to_vm(buffer, &address, sizeof(address));
+ return shbuf_id;
+}
+
+FlatPtr Emulator::virt$shbuf_get(int shbuf_id, FlatPtr size_ptr)
+{
+ size_t host_size = 0;
+ void* host_data = (void*)syscall(SC_shbuf_get, shbuf_id, &host_size);
+ if (host_data == (void*)-1)
+ return (FlatPtr)host_data;
+ FlatPtr address = allocate_vm(host_size, PAGE_SIZE);
+ auto region = SharedBufferRegion::create_with_shbuf_id(address, host_size, shbuf_id, (u8*)host_data);
+ m_mmu.add_region(move(region));
+ m_mmu.copy_to_vm(size_ptr, &host_size, sizeof(host_size));
+ return address;
+}
+
+int Emulator::virt$shbuf_allow_pid(int shbuf_id, pid_t peer_pid)
+{
+ auto* region = m_mmu.shbuf_region(shbuf_id);
+ ASSERT(region);
+ return region->allow_pid(peer_pid);
+}
+
+int Emulator::virt$shbuf_allow_all(int shbuf_id)
+{
+ auto* region = m_mmu.shbuf_region(shbuf_id);
+ ASSERT(region);
+ return region->allow_all();
+}
+
+int Emulator::virt$shbuf_release(int shbuf_id)
+{
+ auto* region = m_mmu.shbuf_region(shbuf_id);
+ ASSERT(region);
+ auto rc = region->release();
+ m_mmu.remove_region(*region);
+ return rc;
+}
+
+int Emulator::virt$shbuf_seal(int shbuf_id)
+{
+ auto* region = m_mmu.shbuf_region(shbuf_id);
+ ASSERT(region);
+ return region->seal();
+}
+
+int Emulator::virt$shbuf_set_volatile(int shbuf_id, bool is_volatile)
+{
+ auto* region = m_mmu.shbuf_region(shbuf_id);
+ ASSERT(region);
+ return region->set_volatile(is_volatile);
+}
+
+int Emulator::virt$profiling_enable(pid_t pid)
+{
+ return syscall(SC_profiling_enable, pid);
+}
+
+int Emulator::virt$profiling_disable(pid_t pid)
+{
+ return syscall(SC_profiling_disable, pid);
+}
+
+int Emulator::virt$disown(pid_t pid)
+{
+ return syscall(SC_disown, pid);
+}
+
+int Emulator::virt$purge(int mode)
+{
+ return syscall(SC_purge, mode);
+}
+
+int Emulator::virt$fstat(int fd, FlatPtr statbuf)
+{
+ struct stat local_statbuf;
+ int rc = syscall(SC_fstat, fd, &local_statbuf);
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(statbuf, &local_statbuf, sizeof(local_statbuf));
+ return rc;
+}
+
+int Emulator::virt$close(int fd)
+{
+ return syscall(SC_close, fd);
+}
+
+int Emulator::virt$mkdir(FlatPtr path, size_t path_length, mode_t mode)
+{
+ auto buffer = mmu().copy_buffer_from_vm(path, path_length);
+ return syscall(SC_mkdir, buffer.data(), buffer.size(), mode);
+}
+
+int Emulator::virt$unlink(FlatPtr path, size_t path_length)
+{
+ auto buffer = mmu().copy_buffer_from_vm(path, path_length);
+ return syscall(SC_unlink, buffer.data(), buffer.size());
+}
+
+int Emulator::virt$dbgputstr(FlatPtr characters, int length)
+{
+ auto buffer = mmu().copy_buffer_from_vm(characters, length);
+ dbgputstr((const char*)buffer.data(), buffer.size());
+ return 0;
+}
+
+int Emulator::virt$chmod(FlatPtr path_addr, size_t path_length, mode_t mode)
+{
+ auto path = mmu().copy_buffer_from_vm(path_addr, path_length);
+ return syscall(SC_chmod, path.data(), path.size(), mode);
+}
+
+int Emulator::virt$fchmod(int fd, mode_t mode)
+{
+ return syscall(SC_fchmod, fd, mode);
+}
+
+int Emulator::virt$fchown(int fd, uid_t uid, gid_t gid)
+{
+ return syscall(SC_fchown, fd, uid, gid);
+}
+
+int Emulator::virt$setsockopt(FlatPtr params_addr)
+{
+ Syscall::SC_setsockopt_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ if (params.option == SO_RCVTIMEO || params.option == SO_TIMESTAMP) {
+ auto host_value_buffer = ByteBuffer::create_zeroed(params.value_size);
+ mmu().copy_from_vm(host_value_buffer.data(), (FlatPtr)params.value, params.value_size);
+ int rc = setsockopt(params.sockfd, params.level, params.option, host_value_buffer.data(), host_value_buffer.size());
+ if (rc < 0)
+ return -errno;
+ return rc;
+ }
+
+ if (params.option == SO_BINDTODEVICE) {
+ auto ifname = mmu().copy_buffer_from_vm((FlatPtr)params.value, params.value_size);
+ params.value = ifname.data();
+ params.value_size = ifname.size();
+ return syscall(SC_setsockopt, &params);
+ }
+
+ TODO();
+}
+
+int Emulator::virt$get_stack_bounds(FlatPtr base, FlatPtr size)
+{
+ auto* region = mmu().find_region({ m_cpu.ss(), m_cpu.esp().value() });
+ FlatPtr b = region->base();
+ size_t s = region->size();
+ mmu().copy_to_vm(base, &b, sizeof(b));
+ mmu().copy_to_vm(size, &s, sizeof(s));
+ return 0;
+}
+
+int Emulator::virt$ftruncate(int fd, off_t length)
+{
+ return syscall(SC_ftruncate, fd, length);
+}
+
+mode_t Emulator::virt$umask(mode_t mask)
+{
+ return syscall(SC_umask, mask);
+}
+
+int Emulator::virt$accept(int sockfd, FlatPtr address, FlatPtr address_length)
+{
+ socklen_t host_address_length = 0;
+ mmu().copy_from_vm(&host_address_length, address_length, sizeof(host_address_length));
+ auto host_buffer = ByteBuffer::create_zeroed(host_address_length);
+ int rc = syscall(SC_accept, sockfd, host_buffer.data(), &host_address_length);
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(address, host_buffer.data(), min((socklen_t)host_buffer.size(), host_address_length));
+ mmu().copy_to_vm(address_length, &host_address_length, sizeof(host_address_length));
+ return rc;
+}
+
+int Emulator::virt$bind(int sockfd, FlatPtr address, socklen_t address_length)
+{
+ auto buffer = mmu().copy_buffer_from_vm(address, address_length);
+ return syscall(SC_bind, sockfd, buffer.data(), buffer.size());
+}
+
+int Emulator::virt$connect(int sockfd, FlatPtr address, socklen_t address_size)
+{
+ auto buffer = mmu().copy_buffer_from_vm(address, address_size);
+ return syscall(SC_connect, sockfd, buffer.data(), buffer.size());
+}
+
+int Emulator::virt$dbgputch(char ch)
+{
+ dbgputch(ch);
+ return 0;
+}
+
+int Emulator::virt$listen(int fd, int backlog)
+{
+ return syscall(SC_listen, fd, backlog);
+}
+
+int Emulator::virt$kill(pid_t pid, int signal)
+{
+ return syscall(SC_kill, pid, signal);
+}
+
+int Emulator::virt$gettimeofday(FlatPtr timeval)
+{
+ struct timeval host_timeval;
+ int rc = syscall(SC_gettimeofday, &host_timeval);
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(timeval, &host_timeval, sizeof(host_timeval));
+ return rc;
+}
+
+int Emulator::virt$clock_gettime(int clockid, FlatPtr timespec)
+{
+ struct timespec host_timespec;
+ int rc = syscall(SC_clock_gettime, clockid, &host_timespec);
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(timespec, &host_timespec, sizeof(host_timespec));
+ return rc;
+}
+
+int Emulator::virt$clock_settime(uint32_t clock_id, FlatPtr user_ts)
+{
+ struct timespec user_timespec;
+ mmu().copy_from_vm(&user_timespec, user_ts, sizeof(user_timespec));
+ int rc = syscall(SC_clock_settime, clock_id, &user_timespec);
+ return rc;
+}
+
+int Emulator::virt$set_mmap_name(FlatPtr)
+{
+ // FIXME: Implement.
+ return 0;
+}
+
+int Emulator::virt$get_process_name(FlatPtr buffer, int size)
+{
+ if (size < 0)
+ return -EINVAL;
+ auto host_buffer = ByteBuffer::create_zeroed((size_t)size);
+ int rc = syscall(SC_get_process_name, host_buffer.data(), host_buffer.size());
+ mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size());
+ return rc;
+}
+
+int Emulator::virt$lseek(int fd, off_t offset, int whence)
+{
+ return syscall(SC_lseek, fd, offset, whence);
+}
+
+int Emulator::virt$socket(int domain, int type, int protocol)
+{
+ return syscall(SC_socket, domain, type, protocol);
+}
+
+int Emulator::virt$recvmsg(int sockfd, FlatPtr msg_addr, int flags)
+{
+ msghdr mmu_msg;
+ mmu().copy_from_vm(&mmu_msg, msg_addr, sizeof(mmu_msg));
+
+ Vector<iovec, 1> mmu_iovs;
+ mmu_iovs.resize(mmu_msg.msg_iovlen);
+ mmu().copy_from_vm(mmu_iovs.data(), (FlatPtr)mmu_msg.msg_iov, mmu_msg.msg_iovlen * sizeof(iovec));
+ Vector<ByteBuffer, 1> buffers;
+ Vector<iovec, 1> iovs;
+ for (const auto& iov : mmu_iovs) {
+ buffers.append(ByteBuffer::create_uninitialized(iov.iov_len));
+ iovs.append({ buffers.last().data(), buffers.last().size() });
+ }
+
+ ByteBuffer control_buffer;
+ if (mmu_msg.msg_control)
+ control_buffer = ByteBuffer::create_uninitialized(mmu_msg.msg_controllen);
+
+ sockaddr_storage addr;
+ msghdr msg = { &addr, sizeof(addr), iovs.data(), (int)iovs.size(), mmu_msg.msg_control ? control_buffer.data() : nullptr, mmu_msg.msg_controllen, mmu_msg.msg_flags };
+ int rc = recvmsg(sockfd, &msg, flags);
+ if (rc < 0)
+ return -errno;
+
+ for (size_t i = 0; i < buffers.size(); ++i)
+ mmu().copy_to_vm((FlatPtr)mmu_iovs[i].iov_base, buffers[i].data(), mmu_iovs[i].iov_len);
+
+ if (mmu_msg.msg_name)
+ mmu().copy_to_vm((FlatPtr)mmu_msg.msg_name, &addr, min(sizeof(addr), (size_t)mmu_msg.msg_namelen));
+ if (mmu_msg.msg_control)
+ mmu().copy_to_vm((FlatPtr)mmu_msg.msg_control, control_buffer.data(), min(mmu_msg.msg_controllen, msg.msg_controllen));
+ mmu_msg.msg_namelen = msg.msg_namelen;
+ mmu_msg.msg_controllen = msg.msg_controllen;
+ mmu_msg.msg_flags = msg.msg_flags;
+ mmu().copy_to_vm(msg_addr, &mmu_msg, sizeof(mmu_msg));
+ return rc;
+}
+
+int Emulator::virt$sendmsg(int sockfd, FlatPtr msg_addr, int flags)
+{
+ msghdr mmu_msg;
+ mmu().copy_from_vm(&mmu_msg, msg_addr, sizeof(mmu_msg));
+
+ Vector<iovec, 1> iovs;
+ iovs.resize(mmu_msg.msg_iovlen);
+ mmu().copy_from_vm(iovs.data(), (FlatPtr)mmu_msg.msg_iov, mmu_msg.msg_iovlen * sizeof(iovec));
+ Vector<ByteBuffer, 1> buffers;
+ for (auto& iov : iovs) {
+ buffers.append(mmu().copy_buffer_from_vm((FlatPtr)iov.iov_base, iov.iov_len));
+ iov = { buffers.last().data(), buffers.last().size() };
+ }
+
+ ByteBuffer control_buffer;
+ if (mmu_msg.msg_control)
+ control_buffer = ByteBuffer::create_uninitialized(mmu_msg.msg_controllen);
+
+ sockaddr_storage address;
+ socklen_t address_length = 0;
+ if (mmu_msg.msg_name) {
+ address_length = min(sizeof(address), (size_t)mmu_msg.msg_namelen);
+ mmu().copy_from_vm(&address, (FlatPtr)mmu_msg.msg_name, address_length);
+ }
+
+ msghdr msg = { mmu_msg.msg_name ? &address : nullptr, address_length, iovs.data(), (int)iovs.size(), mmu_msg.msg_control ? control_buffer.data() : nullptr, mmu_msg.msg_controllen, mmu_msg.msg_flags };
+ return sendmsg(sockfd, &msg, flags);
+}
+
+int Emulator::virt$select(FlatPtr params_addr)
+{
+ Syscall::SC_select_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ fd_set readfds {};
+ fd_set writefds {};
+ fd_set exceptfds {};
+ struct timespec timeout;
+ u32 sigmask;
+
+ if (params.readfds)
+ mmu().copy_from_vm(&readfds, (FlatPtr)params.readfds, sizeof(readfds));
+ if (params.writefds)
+ mmu().copy_from_vm(&writefds, (FlatPtr)params.writefds, sizeof(writefds));
+ if (params.exceptfds)
+ mmu().copy_from_vm(&exceptfds, (FlatPtr)params.exceptfds, sizeof(exceptfds));
+ if (params.timeout)
+ mmu().copy_from_vm(&timeout, (FlatPtr)params.timeout, sizeof(timeout));
+ if (params.sigmask)
+ mmu().copy_from_vm(&sigmask, (FlatPtr)params.sigmask, sizeof(sigmask));
+
+ int rc = pselect(params.nfds, &readfds, &writefds, &exceptfds, params.timeout ? &timeout : nullptr, params.sigmask ? &sigmask : nullptr);
+ if (rc < 0)
+ return -errno;
+
+ if (params.readfds)
+ mmu().copy_to_vm((FlatPtr)params.readfds, &readfds, sizeof(readfds));
+ if (params.writefds)
+ mmu().copy_to_vm((FlatPtr)params.writefds, &writefds, sizeof(writefds));
+ if (params.exceptfds)
+ mmu().copy_to_vm((FlatPtr)params.exceptfds, &exceptfds, sizeof(exceptfds));
+ if (params.timeout)
+ mmu().copy_to_vm((FlatPtr)params.timeout, &timeout, sizeof(timeout));
+
+ return rc;
+}
+
+int Emulator::virt$getsockopt(FlatPtr params_addr)
+{
+ Syscall::SC_getsockopt_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ if (params.option == SO_PEERCRED) {
+ struct ucred creds = {};
+ socklen_t creds_size = sizeof(creds);
+ int rc = getsockopt(params.sockfd, params.level, SO_PEERCRED, &creds, &creds_size);
+ if (rc < 0)
+ return -errno;
+ // FIXME: Check params.value_size
+ mmu().copy_to_vm((FlatPtr)params.value, &creds, sizeof(creds));
+ return rc;
+ }
+
+ TODO();
+}
+
+int Emulator::virt$getgroups(ssize_t count, FlatPtr groups)
+{
+ if (!count)
+ return syscall(SC_getgroups, 0, nullptr);
+
+ auto buffer = ByteBuffer::create_uninitialized(count * sizeof(gid_t));
+ int rc = syscall(SC_getgroups, count, buffer.data());
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(groups, buffer.data(), buffer.size());
+ return 0;
+}
+
+int Emulator::virt$setgroups(ssize_t count, FlatPtr groups)
+{
+ if (!count)
+ return syscall(SC_setgroups, 0, nullptr);
+
+ auto buffer = mmu().copy_buffer_from_vm(groups, count * sizeof(gid_t));
+ return syscall(SC_setgroups, count, buffer.data());
+}
+
+u32 Emulator::virt$fcntl(int fd, int cmd, u32 arg)
+{
+ switch (cmd) {
+ case F_DUPFD:
+ case F_GETFD:
+ case F_SETFD:
+ case F_GETFL:
+ case F_SETFL:
+ case F_ISTTY:
+ break;
+ default:
+ TODO();
+ }
+
+ return syscall(SC_fcntl, fd, cmd, arg);
+}
+
+u32 Emulator::virt$open(u32 params_addr)
+{
+ Syscall::SC_open_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ auto path = mmu().copy_buffer_from_vm((FlatPtr)params.path.characters, params.path.length);
+
+ int fd = openat_with_path_length(params.dirfd, (const char*)path.data(), path.size(), params.options, params.mode);
+ if (fd < 0)
+ return -errno;
+ return fd;
+}
+
+int Emulator::virt$pipe(FlatPtr vm_pipefd, int flags)
+{
+ int pipefd[2];
+ int rc = syscall(SC_pipe, pipefd, flags);
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(vm_pipefd, pipefd, sizeof(pipefd));
+ return rc;
+}
+
+u32 Emulator::virt$munmap(FlatPtr address, u32 size)
+{
+ auto* region = mmu().find_region({ 0x23, address });
+ ASSERT(region);
+ if (region->size() != round_up_to_power_of_two(size, PAGE_SIZE))
+ TODO();
+ mmu().remove_region(*region);
+ return 0;
+}
+
+FlatPtr Emulator::allocate_vm(size_t size, size_t alignment)
+{
+ // FIXME: Write a proper VM allocator
+ static FlatPtr next_address = 0x30000000;
+
+ FlatPtr final_address;
+
+ if (!alignment)
+ alignment = PAGE_SIZE;
+
+ // FIXME: What if alignment is not a power of 2?
+ final_address = round_up_to_power_of_two(next_address, alignment);
+ next_address = round_up_to_power_of_two(final_address + size, PAGE_SIZE);
+ return final_address;
+}
+
+u32 Emulator::virt$mmap(u32 params_addr)
+{
+ Syscall::SC_mmap_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ u32 final_size = round_up_to_power_of_two(params.size, PAGE_SIZE);
+ u32 final_address = allocate_vm(final_size, params.alignment);
+ if (params.addr != 0) {
+ // NOTE: We currently do not support allocating VM at a requeted address in the emulator.
+ // The loader needs this functionality to load .data just after .text.
+ // Luckily, since the loader calls mmap for .data right after it calls mmap for .text,
+ // the emulator will allocate a chunk of memory that is just after what we allocated for .text
+ // because of the way we currently allocate VM.
+ ASSERT(params.addr == final_address);
+ }
+
+ if (params.flags & MAP_ANONYMOUS)
+ mmu().add_region(MmapRegion::create_anonymous(final_address, final_size, params.prot));
+ else {
+ String name_str;
+ if (params.name.characters) {
+ auto name = ByteBuffer::create_uninitialized(params.name.length);
+ mmu().copy_from_vm(name.data(), (FlatPtr)params.name.characters, params.name.length);
+ name_str = { name.data(), name.size() };
+ }
+ auto region = MmapRegion::create_file_backed(final_address, final_size, params.prot, params.flags, params.fd, params.offset, name_str);
+ if (region->name() == "libc.so: .text (Emulated)") {
+ bool rc = find_malloc_symbols(*region);
+ ASSERT(rc);
+ }
+ mmu().add_region(move(region));
+ }
+
+ return final_address;
+}
+
+FlatPtr Emulator::virt$mremap(FlatPtr params_addr)
+{
+ Syscall::SC_mremap_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ if (auto* region = mmu().find_region({ m_cpu.ds(), params.old_address })) {
+ if (!is<MmapRegion>(*region))
+ return -EINVAL;
+ ASSERT(region->size() == params.old_size);
+ auto& mmap_region = *(MmapRegion*)region;
+ auto* ptr = mremap(mmap_region.data(), mmap_region.size(), mmap_region.size(), params.flags);
+ if (ptr == MAP_FAILED)
+ return -errno;
+ return (FlatPtr)ptr;
+ }
+ return -EINVAL;
+}
+
+u32 Emulator::virt$mount(u32 params_addr)
+{
+ Syscall::SC_mount_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+ auto target = mmu().copy_buffer_from_vm((FlatPtr)params.target.characters, params.target.length);
+ auto fs_path = mmu().copy_buffer_from_vm((FlatPtr)params.fs_type.characters, params.fs_type.length);
+ params.fs_type.characters = (char*)fs_path.data();
+ params.fs_type.length = fs_path.size();
+ params.target.characters = (char*)target.data();
+ params.target.length = target.size();
+
+ return syscall(SC_mount, &params);
+}
+
+u32 Emulator::virt$gettid()
+{
+ return gettid();
+}
+
+u32 Emulator::virt$getpid()
+{
+ return getpid();
+}
+
+u32 Emulator::virt$pledge(u32)
+{
+ return 0;
+}
+
+u32 Emulator::virt$unveil(u32)
+{
+ return 0;
+}
+
+u32 Emulator::virt$mprotect(FlatPtr base, size_t size, int prot)
+{
+ if (auto* region = mmu().find_region({ m_cpu.ds(), base })) {
+ if (!is<MmapRegion>(*region))
+ return -EINVAL;
+ ASSERT(region->size() == size);
+ auto& mmap_region = *(MmapRegion*)region;
+ mmap_region.set_prot(prot);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+u32 Emulator::virt$madvise(FlatPtr, size_t, int)
+{
+ return 0;
+}
+
+uid_t Emulator::virt$getuid()
+{
+ return getuid();
+}
+
+uid_t Emulator::virt$geteuid()
+{
+ return geteuid();
+}
+
+gid_t Emulator::virt$getgid()
+{
+ return getgid();
+}
+
+gid_t Emulator::virt$getegid()
+{
+ return getegid();
+}
+
+int Emulator::virt$setuid(uid_t uid)
+{
+ return syscall(SC_setuid, uid);
+}
+
+int Emulator::virt$setgid(gid_t gid)
+{
+ return syscall(SC_setgid, gid);
+}
+
+u32 Emulator::virt$write(int fd, FlatPtr data, ssize_t size)
+{
+ if (size < 0)
+ return -EINVAL;
+ auto buffer = mmu().copy_buffer_from_vm(data, size);
+ return syscall(SC_write, fd, buffer.data(), buffer.size());
+}
+
+u32 Emulator::virt$read(int fd, FlatPtr buffer, ssize_t size)
+{
+ if (size < 0)
+ return -EINVAL;
+ auto local_buffer = ByteBuffer::create_uninitialized(size);
+ int nread = syscall(SC_read, fd, local_buffer.data(), local_buffer.size());
+ if (nread < 0) {
+ if (nread == -EPERM) {
+ dump_backtrace();
+ TODO();
+ }
+ return nread;
+ }
+ mmu().copy_to_vm(buffer, local_buffer.data(), local_buffer.size());
+ return nread;
+}
+
+void Emulator::virt$exit(int status)
+{
+ reportln("\n=={}== \033[33;1mSyscall: exit({})\033[0m, shutting down!", getpid(), status);
+ m_exit_status = status;
+ m_shutdown = true;
+}
+
+ssize_t Emulator::virt$getrandom(FlatPtr buffer, size_t buffer_size, unsigned int flags)
+{
+ auto host_buffer = ByteBuffer::create_uninitialized(buffer_size);
+ int rc = syscall(SC_getrandom, host_buffer.data(), host_buffer.size(), flags);
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size());
+ return rc;
+}
+
+int Emulator::virt$get_dir_entries(int fd, FlatPtr buffer, ssize_t size)
+{
+ auto host_buffer = ByteBuffer::create_uninitialized(size);
+ int rc = syscall(SC_get_dir_entries, fd, host_buffer.data(), host_buffer.size());
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size());
+ return rc;
+}
+
+int Emulator::virt$ioctl([[maybe_unused]] int fd, unsigned request, [[maybe_unused]] FlatPtr arg)
+{
+ if (request == TIOCGWINSZ) {
+ struct winsize ws;
+ int rc = syscall(SC_ioctl, fd, TIOCGWINSZ, &ws);
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(arg, &ws, sizeof(winsize));
+ return 0;
+ }
+ if (request == TIOCSPGRP) {
+ return syscall(SC_ioctl, fd, request, arg);
+ }
+ if (request == TCGETS) {
+ struct termios termios;
+ int rc = syscall(SC_ioctl, fd, request, &termios);
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(arg, &termios, sizeof(termios));
+ return rc;
+ }
+ if (request == TCSETS) {
+ struct termios termios;
+ mmu().copy_from_vm(&termios, arg, sizeof(termios));
+ return syscall(SC_ioctl, fd, request, &termios);
+ }
+ if (request == TIOCNOTTY || request == TIOCSCTTY) {
+ return syscall(SC_ioctl, fd, request, 0);
+ }
+ if (request == FB_IOCTL_GET_SIZE_IN_BYTES) {
+ size_t size = 0;
+ auto rc = syscall(SC_ioctl, fd, request, &size);
+ mmu().copy_to_vm(arg, &size, sizeof(size));
+ return rc;
+ }
+ if (request == FB_IOCTL_SET_RESOLUTION) {
+ FBResolution user_resolution;
+ mmu().copy_from_vm(&user_resolution, arg, sizeof(user_resolution));
+ auto rc = syscall(SC_ioctl, fd, request, &user_resolution);
+ mmu().copy_to_vm(arg, &user_resolution, sizeof(user_resolution));
+ return rc;
+ }
+ if (request == FB_IOCTL_SET_BUFFER) {
+ return syscall(SC_ioctl, fd, request, arg);
+ }
+ reportln("Unsupported ioctl: {}", request);
+ dump_backtrace();
+ TODO();
+}
+
+int Emulator::virt$fork()
+{
+ int rc = fork();
+ if (rc < 0)
+ return -errno;
+ return rc;
+}
+
+int Emulator::virt$execve(FlatPtr params_addr)
+{
+ Syscall::SC_execve_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ auto path = String::copy(mmu().copy_buffer_from_vm((FlatPtr)params.path.characters, params.path.length));
+ Vector<String> arguments;
+ Vector<String> environment;
+
+ auto copy_string_list = [this](auto& output_vector, auto& string_list) {
+ for (size_t i = 0; i < string_list.length; ++i) {
+ Syscall::StringArgument string;
+ mmu().copy_from_vm(&string, (FlatPtr)&string_list.strings[i], sizeof(string));
+ output_vector.append(String::copy(mmu().copy_buffer_from_vm((FlatPtr)string.characters, string.length)));
+ }
+ };
+
+ copy_string_list(arguments, params.arguments);
+ copy_string_list(environment, params.environment);
+
+ reportln("\n=={}== \033[33;1mSyscall:\033[0m execve", getpid());
+ reportln("=={}== @ {}", getpid(), path);
+ for (auto& argument : arguments)
+ reportln("=={}== - {}", getpid(), argument);
+
+ Vector<char*> argv;
+ Vector<char*> envp;
+
+ argv.append(const_cast<char*>("/bin/UserspaceEmulator"));
+ argv.append(const_cast<char*>(path.characters()));
+ if (g_report_to_debug)
+ argv.append(const_cast<char*>("--report-to-debug"));
+ argv.append(const_cast<char*>("--"));
+
+ auto create_string_vector = [](auto& output_vector, auto& input_vector) {
+ for (auto& string : input_vector)
+ output_vector.append(const_cast<char*>(string.characters()));
+ output_vector.append(nullptr);
+ };
+
+ create_string_vector(argv, arguments);
+ create_string_vector(envp, environment);
+
+ // Yoink duplicated program name.
+ argv.remove(3 + (g_report_to_debug ? 1 : 0));
+
+ return execve(argv[0], (char* const*)argv.data(), (char* const*)envp.data());
+}
+
+int Emulator::virt$stat(FlatPtr params_addr)
+{
+ Syscall::SC_stat_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ auto path = String::copy(mmu().copy_buffer_from_vm((FlatPtr)params.path.characters, params.path.length));
+ struct stat host_statbuf;
+ int rc;
+ if (params.follow_symlinks)
+ rc = stat(path.characters(), &host_statbuf);
+ else
+ rc = lstat(path.characters(), &host_statbuf);
+ if (rc < 0)
+ return -errno;
+ mmu().copy_to_vm((FlatPtr)params.statbuf, &host_statbuf, sizeof(host_statbuf));
+ return rc;
+}
+
+int Emulator::virt$realpath(FlatPtr params_addr)
+{
+ Syscall::SC_realpath_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ if (params.path.length > PATH_MAX) {
+ return -ENAMETOOLONG;
+ }
+ auto path = mmu().copy_buffer_from_vm((FlatPtr)params.path.characters, params.path.length);
+ char host_buffer[PATH_MAX] = {};
+ size_t host_buffer_size = min(sizeof(host_buffer), params.buffer.size);
+
+ Syscall::SC_realpath_params host_params;
+ host_params.path = { (const char*)path.data(), path.size() };
+ host_params.buffer = { host_buffer, host_buffer_size };
+ int rc = syscall(SC_realpath, &host_params);
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm((FlatPtr)params.buffer.data, host_buffer, host_buffer_size);
+ return rc;
+}
+
+int Emulator::virt$gethostname(FlatPtr buffer, ssize_t buffer_size)
+{
+ if (buffer_size < 0)
+ return -EINVAL;
+ auto host_buffer = ByteBuffer::create_zeroed(buffer_size);
+ int rc = syscall(SC_gethostname, host_buffer.data(), host_buffer.size());
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size());
+ return rc;
+}
+
+static void emulator_signal_handler(int signum)
+{
+ Emulator::the().did_receive_signal(signum);
+}
+
+void Emulator::register_signal_handlers()
+{
+ for (int signum = 0; signum < NSIG; ++signum)
+ signal(signum, emulator_signal_handler);
+}
+
+int Emulator::virt$sigaction(int signum, FlatPtr act, FlatPtr oldact)
+{
+ if (signum == SIGKILL) {
+ reportln("Attempted to sigaction() with SIGKILL");
+ return -EINVAL;
+ }
+
+ if (signum <= 0 || signum >= NSIG)
+ return -EINVAL;
+
+ struct sigaction host_act;
+ mmu().copy_from_vm(&host_act, act, sizeof(host_act));
+
+ auto& handler = m_signal_handler[signum];
+ handler.handler = (FlatPtr)host_act.sa_handler;
+ handler.mask = host_act.sa_mask;
+ handler.flags = host_act.sa_flags;
+
+ if (oldact) {
+ struct sigaction host_oldact;
+ auto& old_handler = m_signal_handler[signum];
+ host_oldact.sa_handler = (void (*)(int))(old_handler.handler);
+ host_oldact.sa_mask = old_handler.mask;
+ host_oldact.sa_flags = old_handler.flags;
+ mmu().copy_to_vm(oldact, &host_oldact, sizeof(host_oldact));
+ }
+ return 0;
+}
+
+int Emulator::virt$sigreturn()
+{
+ u32 stack_ptr = m_cpu.esp().value();
+ auto local_pop = [&]() -> ValueWithShadow<u32> {
+ auto value = m_cpu.read_memory32({ m_cpu.ss(), stack_ptr });
+ stack_ptr += sizeof(u32);
+ return value;
+ };
+
+ auto smuggled_eax = local_pop();
+
+ stack_ptr += 4 * sizeof(u32);
+
+ m_signal_mask = local_pop().value();
+
+ m_cpu.set_edi(local_pop());
+ m_cpu.set_esi(local_pop());
+ m_cpu.set_ebp(local_pop());
+ m_cpu.set_esp(local_pop());
+ m_cpu.set_ebx(local_pop());
+ m_cpu.set_edx(local_pop());
+ m_cpu.set_ecx(local_pop());
+ m_cpu.set_eax(local_pop());
+
+ m_cpu.set_eip(local_pop().value());
+ m_cpu.set_eflags(local_pop());
+
+ // FIXME: We're losing shadow bits here.
+ return smuggled_eax.value();
+}
+
+enum class DefaultSignalAction {
+ Terminate,
+ Ignore,
+ DumpCore,
+ Stop,
+ Continue,
+};
+
+static DefaultSignalAction default_signal_action(int signal)
+{
+ ASSERT(signal && signal < NSIG);
+
+ switch (signal) {
+ case SIGHUP:
+ case SIGINT:
+ case SIGKILL:
+ case SIGPIPE:
+ case SIGALRM:
+ case SIGUSR1:
+ case SIGUSR2:
+ case SIGVTALRM:
+ case SIGSTKFLT:
+ case SIGIO:
+ case SIGPROF:
+ case SIGTERM:
+ return DefaultSignalAction::Terminate;
+ case SIGCHLD:
+ case SIGURG:
+ case SIGWINCH:
+ case SIGINFO:
+ return DefaultSignalAction::Ignore;
+ case SIGQUIT:
+ case SIGILL:
+ case SIGTRAP:
+ case SIGABRT:
+ case SIGBUS:
+ case SIGFPE:
+ case SIGSEGV:
+ case SIGXCPU:
+ case SIGXFSZ:
+ case SIGSYS:
+ return DefaultSignalAction::DumpCore;
+ case SIGCONT:
+ return DefaultSignalAction::Continue;
+ case SIGSTOP:
+ case SIGTSTP:
+ case SIGTTIN:
+ case SIGTTOU:
+ return DefaultSignalAction::Stop;
+ }
+ ASSERT_NOT_REACHED();
+}
+
+void Emulator::dispatch_one_pending_signal()
+{
+ int signum = -1;
+ for (signum = 1; signum < NSIG; ++signum) {
+ int mask = 1 << signum;
+ if (m_pending_signals & mask)
+ break;
+ }
+ ASSERT(signum != -1);
+ m_pending_signals &= ~(1 << signum);
+
+ auto& handler = m_signal_handler[signum];
+
+ if (handler.handler == 0) {
+ // SIG_DFL
+ auto action = default_signal_action(signum);
+ if (action == DefaultSignalAction::Ignore)
+ return;
+ reportln("\n=={}== Got signal {} ({}), no handler registered", getpid(), signum, strsignal(signum));
+ m_shutdown = true;
+ return;
+ }
+
+ if (handler.handler == 1) {
+ // SIG_IGN
+ return;
+ }
+
+ reportln("\n=={}== Got signal {} ({}), handler at {:p}", getpid(), signum, strsignal(signum), handler.handler);
+
+ auto old_esp = m_cpu.esp();
+
+ u32 stack_alignment = (m_cpu.esp().value() - 56) % 16;
+ m_cpu.set_esp(shadow_wrap_as_initialized(m_cpu.esp().value() - stack_alignment));
+
+ m_cpu.push32(shadow_wrap_as_initialized(m_cpu.eflags()));
+ m_cpu.push32(shadow_wrap_as_initialized(m_cpu.eip()));
+ m_cpu.push32(m_cpu.eax());
+ m_cpu.push32(m_cpu.ecx());
+ m_cpu.push32(m_cpu.edx());
+ m_cpu.push32(m_cpu.ebx());
+ m_cpu.push32(old_esp);
+ m_cpu.push32(m_cpu.ebp());
+ m_cpu.push32(m_cpu.esi());
+ m_cpu.push32(m_cpu.edi());
+
+ // FIXME: Push old signal mask here.
+ m_cpu.push32(shadow_wrap_as_initialized(0u));
+
+ m_cpu.push32(shadow_wrap_as_initialized((u32)signum));
+ m_cpu.push32(shadow_wrap_as_initialized(handler.handler));
+ m_cpu.push32(shadow_wrap_as_initialized(0u));
+
+ ASSERT((m_cpu.esp().value() % 16) == 0);
+
+ m_cpu.set_eip(m_signal_trampoline);
+}
+
+// Make sure the compiler doesn't "optimize away" this function:
+extern void signal_trampoline_dummy();
+void signal_trampoline_dummy()
+{
+ // The trampoline preserves the current eax, pushes the signal code and
+ // then calls the signal handler. We do this because, when interrupting a
+ // blocking syscall, that syscall may return some special error code in eax;
+ // This error code would likely be overwritten by the signal handler, so it's
+ // necessary to preserve it here.
+ asm(
+ ".intel_syntax noprefix\n"
+ "asm_signal_trampoline:\n"
+ "push ebp\n"
+ "mov ebp, esp\n"
+ "push eax\n" // we have to store eax 'cause it might be the return value from a syscall
+ "sub esp, 4\n" // align the stack to 16 bytes
+ "mov eax, [ebp+12]\n" // push the signal code
+ "push eax\n"
+ "call [ebp+8]\n" // call the signal handler
+ "add esp, 8\n"
+ "mov eax, %P0\n"
+ "int 0x82\n" // sigreturn syscall
+ "asm_signal_trampoline_end:\n"
+ ".att_syntax" ::"i"(Syscall::SC_sigreturn));
+}
+
+extern "C" void asm_signal_trampoline(void);
+extern "C" void asm_signal_trampoline_end(void);
+
+void Emulator::setup_signal_trampoline()
+{
+ auto trampoline_region = make<SimpleRegion>(0xb0000000, 4096);
+
+ u8* trampoline = (u8*)asm_signal_trampoline;
+ u8* trampoline_end = (u8*)asm_signal_trampoline_end;
+ size_t trampoline_size = trampoline_end - trampoline;
+
+ u8* code_ptr = trampoline_region->data();
+ memcpy(code_ptr, trampoline, trampoline_size);
+
+ m_signal_trampoline = trampoline_region->base();
+ mmu().add_region(move(trampoline_region));
+}
+
+int Emulator::virt$getpgrp()
+{
+ return syscall(SC_getpgrp);
+}
+
+int Emulator::virt$getpgid(pid_t pid)
+{
+ return syscall(SC_getpgid, pid);
+}
+
+int Emulator::virt$setpgid(pid_t pid, pid_t pgid)
+{
+ return syscall(SC_setpgid, pid, pgid);
+}
+
+int Emulator::virt$ttyname(int fd, FlatPtr buffer, size_t buffer_size)
+{
+ auto host_buffer = ByteBuffer::create_zeroed(buffer_size);
+ int rc = syscall(SC_ttyname, fd, host_buffer.data(), host_buffer.size());
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size());
+ return rc;
+}
+
+int Emulator::virt$getcwd(FlatPtr buffer, size_t buffer_size)
+{
+ auto host_buffer = ByteBuffer::create_zeroed(buffer_size);
+ int rc = syscall(SC_getcwd, host_buffer.data(), host_buffer.size());
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size());
+ return rc;
+}
+
+int Emulator::virt$getsid(pid_t pid)
+{
+ return syscall(SC_getsid, pid);
+}
+
+int Emulator::virt$access(FlatPtr path, size_t path_length, int type)
+{
+ auto host_path = mmu().copy_buffer_from_vm(path, path_length);
+ return syscall(SC_access, host_path.data(), host_path.size(), type);
+}
+
+int Emulator::virt$waitid(FlatPtr params_addr)
+{
+ Syscall::SC_waitid_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ Syscall::SC_waitid_params host_params = params;
+ siginfo info;
+ host_params.infop = &info;
+
+ int rc = syscall(SC_waitid, &host_params);
+ if (rc < 0)
+ return rc;
+
+ if (info.si_addr) {
+ // FIXME: Translate this somehow.
+ TODO();
+ }
+
+ if (params.infop)
+ mmu().copy_to_vm((FlatPtr)params.infop, &info, sizeof(info));
+
+ return rc;
+}
+
+int Emulator::virt$chdir(FlatPtr path, size_t path_length)
+{
+ auto host_path = mmu().copy_buffer_from_vm(path, path_length);
+ return syscall(SC_chdir, host_path.data(), host_path.size());
+}
+
+int Emulator::virt$dup2(int old_fd, int new_fd)
+{
+ return syscall(SC_dup2, old_fd, new_fd);
+}
+
+int Emulator::virt$sched_getparam(pid_t pid, FlatPtr user_addr)
+{
+ sched_param user_param;
+ mmu().copy_from_vm(&user_param, user_addr, sizeof(user_param));
+ auto rc = syscall(SC_sched_getparam, pid, &user_param);
+ mmu().copy_to_vm(user_addr, &user_param, sizeof(user_param));
+ return rc;
+}
+
+int Emulator::virt$sched_setparam(int pid, FlatPtr user_addr)
+{
+ sched_param user_param;
+ mmu().copy_from_vm(&user_param, user_addr, sizeof(user_param));
+ return syscall(SC_sched_setparam, pid, &user_param);
+}
+
+int Emulator::virt$set_thread_name(pid_t pid, FlatPtr name_addr, size_t name_length)
+{
+ auto user_name = mmu().copy_buffer_from_vm(name_addr, name_length);
+ auto name = String::formatted("(UE) {}", StringView { user_name.data(), user_name.size() });
+ return syscall(SC_set_thread_name, pid, name.characters(), name.length());
+}
+
+pid_t Emulator::virt$setsid()
+{
+ return syscall(SC_setsid);
+}
+
+int Emulator::virt$watch_file(FlatPtr user_path_addr, size_t path_length)
+{
+ auto user_path = mmu().copy_buffer_from_vm(user_path_addr, path_length);
+ return syscall(SC_watch_file, user_path.data(), user_path.size());
+}
+
+int Emulator::virt$clock_nanosleep(FlatPtr params_addr)
+{
+ Syscall::SC_clock_nanosleep_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ timespec requested_sleep;
+ mmu().copy_from_vm(&requested_sleep, (FlatPtr)params.requested_sleep, sizeof(timespec));
+ params.requested_sleep = &requested_sleep;
+
+ auto remaining_vm_addr = params.remaining_sleep;
+ auto remaining = ByteBuffer::create_zeroed(sizeof(timespec));
+ params.remaining_sleep = (timespec*)remaining.data();
+
+ int rc = syscall(SC_clock_nanosleep, &params);
+ if (remaining_vm_addr)
+ mmu().copy_to_vm((FlatPtr)remaining_vm_addr, remaining.data(), sizeof(timespec));
+
+ return rc;
+}
+
+int Emulator::virt$readlink(FlatPtr params_addr)
+{
+ Syscall::SC_readlink_params params;
+ mmu().copy_from_vm(&params, params_addr, sizeof(params));
+
+ if (params.path.length > PATH_MAX) {
+ return -ENAMETOOLONG;
+ }
+ auto path = mmu().copy_buffer_from_vm((FlatPtr)params.path.characters, params.path.length);
+ char host_buffer[PATH_MAX] = {};
+ size_t host_buffer_size = min(sizeof(host_buffer), params.buffer.size);
+
+ Syscall::SC_readlink_params host_params;
+ host_params.path = { (const char*)path.data(), path.size() };
+ host_params.buffer = { host_buffer, host_buffer_size };
+ int rc = syscall(SC_readlink, &host_params);
+ if (rc < 0)
+ return rc;
+ mmu().copy_to_vm((FlatPtr)params.buffer.data, host_buffer, host_buffer_size);
+ return rc;
+}
+
+u32 Emulator::virt$allocate_tls(size_t size)
+{
+ // TODO: Why is this needed? without this, the loader overflows the bounds of the TLS region.
+ constexpr size_t TLS_SIZE_HACK = 8;
+ auto tcb_region = make<SimpleRegion>(0x20000000, size + TLS_SIZE_HACK);
+ bzero(tcb_region->data(), size);
+ memset(tcb_region->shadow_data(), 0x01, size);
+
+ auto tls_region = make<SimpleRegion>(0, 4);
+ tls_region->write32(0, shadow_wrap_as_initialized(tcb_region->base() + (u32)size));
+ memset(tls_region->shadow_data(), 0x01, 4);
+
+ u32 tls_base = tcb_region->base();
+ mmu().add_region(move(tcb_region));
+ mmu().set_tls_region(move(tls_region));
+ return tls_base;
+}
+
+int Emulator::virt$ptsname(int fd, FlatPtr buffer, size_t buffer_size)
+{
+ auto pts = mmu().copy_buffer_from_vm(buffer, buffer_size);
+ return syscall(SC_ptsname, fd, pts.data(), pts.size());
+}
+
+int Emulator::virt$beep()
+{
+ return syscall(SC_beep);
+}
+
+bool Emulator::find_malloc_symbols(const MmapRegion& libc_text)
+{
+ auto file_or_error = MappedFile::map("/usr/lib/libc.so");
+ if (file_or_error.is_error())
+ return false;
+
+ ELF::Image image(file_or_error.value()->bytes());
+ auto malloc_symbol = image.find_demangled_function("malloc");
+ auto free_symbol = image.find_demangled_function("free");
+ auto realloc_symbol = image.find_demangled_function("realloc");
+ auto malloc_size_symbol = image.find_demangled_function("malloc_size");
+ if (!malloc_symbol.has_value() || !free_symbol.has_value() || !realloc_symbol.has_value() || !malloc_size_symbol.has_value())
+ return false;
+
+ m_malloc_symbol_start = malloc_symbol.value().value() + libc_text.base();
+ m_malloc_symbol_end = m_malloc_symbol_start + malloc_symbol.value().size();
+ m_free_symbol_start = free_symbol.value().value() + libc_text.base();
+ m_free_symbol_end = m_free_symbol_start + free_symbol.value().size();
+ m_realloc_symbol_start = realloc_symbol.value().value() + libc_text.base();
+ m_realloc_symbol_end = m_realloc_symbol_start + realloc_symbol.value().size();
+ m_malloc_size_symbol_start = malloc_size_symbol.value().value() + libc_text.base();
+ m_malloc_size_symbol_end = m_malloc_size_symbol_start + malloc_size_symbol.value().size();
+ return true;
+}
+}
diff --git a/Userland/DevTools/UserspaceEmulator/Emulator.h b/Userland/DevTools/UserspaceEmulator/Emulator.h
new file mode 100644
index 0000000000..ff8fad1235
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/Emulator.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "MallocTracer.h"
+#include "Report.h"
+#include "SoftCPU.h"
+#include "SoftMMU.h"
+#include <AK/MappedFile.h>
+#include <AK/Types.h>
+#include <LibDebug/DebugInfo.h>
+#include <LibELF/AuxiliaryVector.h>
+#include <LibELF/Image.h>
+#include <LibX86/Instruction.h>
+#include <signal.h>
+#include <sys/types.h>
+
+namespace UserspaceEmulator {
+
+class MallocTracer;
+
+class Emulator {
+public:
+ static Emulator& the();
+
+ Emulator(const String& executable_path, const Vector<String>& arguments, const Vector<String>& environment);
+
+ bool load_elf();
+ void dump_backtrace();
+ void dump_backtrace(const Vector<FlatPtr>&);
+ Vector<FlatPtr> raw_backtrace();
+
+ int exec();
+ u32 virt_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3);
+
+ SoftMMU& mmu() { return m_mmu; }
+
+ MallocTracer* malloc_tracer() { return m_malloc_tracer; }
+
+ bool is_in_malloc_or_free() const;
+ bool is_in_loader_code() const;
+
+ void did_receive_signal(int signum) { m_pending_signals |= (1 << signum); }
+
+private:
+ const String m_executable_path;
+ const Vector<String> m_arguments;
+ const Vector<String> m_environment;
+
+ SoftMMU m_mmu;
+ SoftCPU m_cpu;
+
+ OwnPtr<MallocTracer> m_malloc_tracer;
+
+ void setup_stack(Vector<ELF::AuxiliaryValue>);
+ Vector<ELF::AuxiliaryValue> generate_auxiliary_vector(FlatPtr load_base, FlatPtr entry_eip, String executable_path, int executable_fd) const;
+ void register_signal_handlers();
+ void setup_signal_trampoline();
+
+ int virt$fork();
+ int virt$execve(FlatPtr);
+ int virt$access(FlatPtr, size_t, int);
+ int virt$sigaction(int, FlatPtr, FlatPtr);
+ int virt$sigreturn();
+ int virt$get_dir_entries(int fd, FlatPtr buffer, ssize_t);
+ int virt$ioctl(int fd, unsigned, FlatPtr);
+ int virt$stat(FlatPtr);
+ int virt$realpath(FlatPtr);
+ int virt$gethostname(FlatPtr, ssize_t);
+ int virt$shbuf_create(int size, FlatPtr buffer);
+ int virt$shbuf_allow_pid(int, pid_t peer_pid);
+ int virt$shbuf_allow_all(int);
+ FlatPtr virt$shbuf_get(int shbuf_id, FlatPtr size);
+ int virt$shbuf_release(int shbuf_id);
+ int virt$shbuf_seal(int shbuf_id);
+ int virt$shbuf_set_volatile(int shbuf_id, bool);
+ int virt$profiling_enable(pid_t);
+ int virt$profiling_disable(pid_t);
+ int virt$disown(pid_t);
+ int virt$purge(int mode);
+ u32 virt$mmap(u32);
+ FlatPtr virt$mremap(FlatPtr);
+ u32 virt$mount(u32);
+ u32 virt$munmap(FlatPtr address, u32 size);
+ u32 virt$gettid();
+ u32 virt$getpid();
+ u32 virt$unveil(u32);
+ u32 virt$pledge(u32);
+ uid_t virt$getuid();
+ uid_t virt$geteuid();
+ gid_t virt$getgid();
+ gid_t virt$getegid();
+ int virt$setuid(uid_t);
+ int virt$setgid(gid_t);
+ u32 virt$read(int, FlatPtr, ssize_t);
+ u32 virt$write(int, FlatPtr, ssize_t);
+ u32 virt$mprotect(FlatPtr, size_t, int);
+ u32 virt$madvise(FlatPtr, size_t, int);
+ u32 virt$open(u32);
+ int virt$pipe(FlatPtr pipefd, int flags);
+ int virt$close(int);
+ int virt$mkdir(FlatPtr path, size_t path_length, mode_t mode);
+ int virt$unlink(FlatPtr path, size_t path_length);
+ int virt$get_process_name(FlatPtr buffer, int size);
+ int virt$set_mmap_name(FlatPtr);
+ int virt$gettimeofday(FlatPtr);
+ int virt$clock_gettime(int, FlatPtr);
+ int virt$clock_nanosleep(FlatPtr);
+ int virt$dbgputstr(FlatPtr characters, int length);
+ int virt$dbgputch(char);
+ int virt$chmod(FlatPtr, size_t, mode_t);
+ int virt$fchmod(int, mode_t);
+ int virt$fchown(int, uid_t, gid_t);
+ int virt$clock_settime(uint32_t clock_id, FlatPtr user_ts);
+ int virt$listen(int, int);
+ int virt$kill(pid_t, int);
+ int virt$fstat(int, FlatPtr);
+ u32 virt$fcntl(int fd, int, u32);
+ int virt$getgroups(ssize_t count, FlatPtr);
+ int virt$setgroups(ssize_t count, FlatPtr);
+ int virt$lseek(int fd, off_t offset, int whence);
+ int virt$socket(int, int, int);
+ int virt$getsockopt(FlatPtr);
+ int virt$setsockopt(FlatPtr);
+ int virt$select(FlatPtr);
+ int virt$get_stack_bounds(FlatPtr, FlatPtr);
+ int virt$accept(int sockfd, FlatPtr address, FlatPtr address_length);
+ int virt$bind(int sockfd, FlatPtr address, socklen_t address_length);
+ int virt$recvmsg(int sockfd, FlatPtr msg_addr, int flags);
+ int virt$sendmsg(int sockfd, FlatPtr msg_addr, int flags);
+ int virt$connect(int sockfd, FlatPtr address, socklen_t address_size);
+ void virt$exit(int);
+ ssize_t virt$getrandom(FlatPtr buffer, size_t buffer_size, unsigned int flags);
+ int virt$chdir(FlatPtr, size_t);
+ int virt$dup2(int, int);
+ int virt$getpgrp();
+ int virt$getpgid(pid_t);
+ int virt$setpgid(pid_t pid, pid_t pgid);
+ int virt$ttyname(int fd, FlatPtr buffer, size_t buffer_size);
+ int virt$getcwd(FlatPtr buffer, size_t buffer_size);
+ int virt$waitid(FlatPtr);
+ int virt$getsid(pid_t);
+ int virt$sched_setparam(int, FlatPtr);
+ int virt$sched_getparam(pid_t, FlatPtr);
+ int virt$set_thread_name(pid_t, FlatPtr, size_t);
+ pid_t virt$setsid();
+ int virt$watch_file(FlatPtr, size_t);
+ int virt$readlink(FlatPtr);
+ u32 virt$allocate_tls(size_t);
+ int virt$ptsname(int fd, FlatPtr buffer, size_t buffer_size);
+ int virt$beep();
+ int virt$ftruncate(int fd, off_t);
+ mode_t virt$umask(mode_t);
+
+ FlatPtr allocate_vm(size_t size, size_t alignment);
+ bool find_malloc_symbols(const MmapRegion& libc_text);
+
+ void dispatch_one_pending_signal();
+ const MmapRegion* find_text_region(FlatPtr address);
+ String create_backtrace_line(FlatPtr address);
+
+ bool m_shutdown { false };
+ int m_exit_status { 0 };
+
+ FlatPtr m_malloc_symbol_start { 0 };
+ FlatPtr m_malloc_symbol_end { 0 };
+ FlatPtr m_realloc_symbol_start { 0 };
+ FlatPtr m_realloc_symbol_end { 0 };
+ FlatPtr m_free_symbol_start { 0 };
+ FlatPtr m_free_symbol_end { 0 };
+ FlatPtr m_malloc_size_symbol_start { 0 };
+ FlatPtr m_malloc_size_symbol_end { 0 };
+
+ sigset_t m_pending_signals { 0 };
+ sigset_t m_signal_mask { 0 };
+
+ struct SignalHandlerInfo {
+ FlatPtr handler { 0 };
+ sigset_t mask { 0 };
+ int flags { 0 };
+ };
+ SignalHandlerInfo m_signal_handler[NSIG];
+
+ FlatPtr m_signal_trampoline { 0 };
+ Optional<FlatPtr> m_loader_text_base;
+ Optional<size_t> m_loader_text_size;
+
+ struct CachedELF {
+ NonnullRefPtr<MappedFile> mapped_file;
+ NonnullOwnPtr<Debug::DebugInfo> debug_info;
+ };
+
+ HashMap<String, CachedELF> m_dynamic_library_cache;
+};
+
+ALWAYS_INLINE bool Emulator::is_in_malloc_or_free() const
+{
+ return (m_cpu.base_eip() >= m_malloc_symbol_start && m_cpu.base_eip() < m_malloc_symbol_end)
+ || (m_cpu.base_eip() >= m_free_symbol_start && m_cpu.base_eip() < m_free_symbol_end)
+ || (m_cpu.base_eip() >= m_realloc_symbol_start && m_cpu.base_eip() < m_realloc_symbol_end)
+ || (m_cpu.base_eip() >= m_malloc_size_symbol_start && m_cpu.base_eip() < m_malloc_size_symbol_end);
+}
+
+ALWAYS_INLINE bool Emulator::is_in_loader_code() const
+{
+ if (!m_loader_text_base.has_value() || !m_loader_text_size.has_value())
+ return false;
+ return (m_cpu.base_eip() >= m_loader_text_base.value() && m_cpu.base_eip() < m_loader_text_base.value() + m_loader_text_size.value());
+}
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/MallocTracer.cpp b/Userland/DevTools/UserspaceEmulator/MallocTracer.cpp
new file mode 100644
index 0000000000..c2ead0409d
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/MallocTracer.cpp
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "MallocTracer.h"
+#include "Emulator.h"
+#include "MmapRegion.h"
+#include <AK/LogStream.h>
+#include <AK/TemporaryChange.h>
+#include <mallocdefs.h>
+#include <string.h>
+
+//#define REACHABLE_DEBUG
+
+namespace UserspaceEmulator {
+
+MallocTracer::MallocTracer(Emulator& emulator)
+ : m_emulator(emulator)
+{
+}
+
+template<typename Callback>
+inline void MallocTracer::for_each_mallocation(Callback callback) const
+{
+ m_emulator.mmu().for_each_region([&](auto& region) {
+ if (is<MmapRegion>(region) && static_cast<const MmapRegion&>(region).is_malloc_block()) {
+ auto* malloc_data = static_cast<MmapRegion&>(region).malloc_metadata();
+ for (auto& mallocation : malloc_data->mallocations) {
+ if (mallocation.used && callback(mallocation) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ }
+ return IterationDecision::Continue;
+ });
+}
+
+void MallocTracer::target_did_malloc(Badge<SoftCPU>, FlatPtr address, size_t size)
+{
+ if (m_emulator.is_in_loader_code())
+ return;
+ auto* region = m_emulator.mmu().find_region({ 0x23, address });
+ ASSERT(region);
+ ASSERT(is<MmapRegion>(*region));
+ auto& mmap_region = static_cast<MmapRegion&>(*region);
+
+ // Mark the containing mmap region as a malloc block!
+ mmap_region.set_malloc(true);
+
+ auto* shadow_bits = mmap_region.shadow_data() + address - mmap_region.base();
+ memset(shadow_bits, 0, size);
+
+ if (auto* existing_mallocation = find_mallocation(address)) {
+ ASSERT(existing_mallocation->freed);
+ existing_mallocation->size = size;
+ existing_mallocation->freed = false;
+ existing_mallocation->malloc_backtrace = m_emulator.raw_backtrace();
+ existing_mallocation->free_backtrace.clear();
+ return;
+ }
+
+ MallocRegionMetadata* malloc_data = static_cast<MmapRegion&>(*region).malloc_metadata();
+ if (!malloc_data) {
+ auto new_malloc_data = make<MallocRegionMetadata>();
+ malloc_data = new_malloc_data.ptr();
+ static_cast<MmapRegion&>(*region).set_malloc_metadata({}, move(new_malloc_data));
+ malloc_data->address = region->base();
+ malloc_data->chunk_size = mmap_region.read32(offsetof(CommonHeader, m_size)).value();
+
+ bool is_chunked_block = malloc_data->chunk_size <= size_classes[num_size_classes - 1];
+ if (is_chunked_block)
+ malloc_data->mallocations.resize((ChunkedBlock::block_size - sizeof(ChunkedBlock)) / malloc_data->chunk_size);
+ else
+ malloc_data->mallocations.resize(1);
+ dbgln("Tracking malloc block @ {:p} with chunk_size={}, chunk_count={}", malloc_data->address, malloc_data->chunk_size, malloc_data->mallocations.size());
+ }
+ malloc_data->mallocation_for_address(address) = { address, size, true, false, m_emulator.raw_backtrace(), Vector<FlatPtr>() };
+}
+
+ALWAYS_INLINE Mallocation& MallocRegionMetadata::mallocation_for_address(FlatPtr address) const
+{
+ return const_cast<Mallocation&>(this->mallocations[chunk_index_for_address(address)]);
+}
+
+ALWAYS_INLINE size_t MallocRegionMetadata::chunk_index_for_address(FlatPtr address) const
+{
+ bool is_chunked_block = chunk_size <= size_classes[num_size_classes - 1];
+ if (!is_chunked_block) {
+ // This is a BigAllocationBlock
+ return 0;
+ }
+ auto chunk_offset = address - (this->address + sizeof(ChunkedBlock));
+ ASSERT(this->chunk_size);
+ return chunk_offset / this->chunk_size;
+}
+
+void MallocTracer::target_did_free(Badge<SoftCPU>, FlatPtr address)
+{
+ if (!address)
+ return;
+ if (m_emulator.is_in_loader_code())
+ return;
+
+ if (auto* mallocation = find_mallocation(address)) {
+ if (mallocation->freed) {
+ reportln("\n=={}== \033[31;1mDouble free()\033[0m, {:p}", getpid(), address);
+ reportln("=={}== Address {} has already been passed to free()", getpid(), address);
+ m_emulator.dump_backtrace();
+ } else {
+ mallocation->freed = true;
+ mallocation->free_backtrace = m_emulator.raw_backtrace();
+ }
+ return;
+ }
+
+ reportln("\n=={}== \033[31;1mInvalid free()\033[0m, {:p}", getpid(), address);
+ reportln("=={}== Address {} has never been returned by malloc()", getpid(), address);
+ m_emulator.dump_backtrace();
+}
+
+void MallocTracer::target_did_realloc(Badge<SoftCPU>, FlatPtr address, size_t size)
+{
+ if (m_emulator.is_in_loader_code())
+ return;
+ auto* region = m_emulator.mmu().find_region({ 0x23, address });
+ ASSERT(region);
+ ASSERT(is<MmapRegion>(*region));
+ auto& mmap_region = static_cast<MmapRegion&>(*region);
+
+ ASSERT(mmap_region.is_malloc_block());
+
+ auto* existing_mallocation = find_mallocation(address);
+ ASSERT(existing_mallocation);
+ ASSERT(!existing_mallocation->freed);
+
+ size_t old_size = existing_mallocation->size;
+
+ auto* shadow_bits = mmap_region.shadow_data() + address - mmap_region.base();
+
+ if (size > old_size) {
+ memset(shadow_bits + old_size, 1, size - old_size);
+ } else {
+ memset(shadow_bits + size, 1, old_size - size);
+ }
+
+ existing_mallocation->size = size;
+ // FIXME: Should we track malloc/realloc backtrace separately perhaps?
+ existing_mallocation->malloc_backtrace = m_emulator.raw_backtrace();
+}
+
+Mallocation* MallocTracer::find_mallocation(FlatPtr address)
+{
+ auto* region = m_emulator.mmu().find_region({ 0x23, address });
+ if (!region)
+ return nullptr;
+ return find_mallocation(*region, address);
+}
+
+Mallocation* MallocTracer::find_mallocation_before(FlatPtr address)
+{
+ Mallocation* found_mallocation = nullptr;
+ for_each_mallocation([&](auto& mallocation) {
+ if (mallocation.address >= address)
+ return IterationDecision::Continue;
+ if (!found_mallocation || (mallocation.address > found_mallocation->address))
+ found_mallocation = const_cast<Mallocation*>(&mallocation);
+ return IterationDecision::Continue;
+ });
+ return found_mallocation;
+}
+
+Mallocation* MallocTracer::find_mallocation_after(FlatPtr address)
+{
+ Mallocation* found_mallocation = nullptr;
+ for_each_mallocation([&](auto& mallocation) {
+ if (mallocation.address <= address)
+ return IterationDecision::Continue;
+ if (!found_mallocation || (mallocation.address < found_mallocation->address))
+ found_mallocation = const_cast<Mallocation*>(&mallocation);
+ return IterationDecision::Continue;
+ });
+ return found_mallocation;
+}
+
+void MallocTracer::audit_read(const Region& region, FlatPtr address, size_t size)
+{
+ if (!m_auditing_enabled)
+ return;
+
+ if (m_emulator.is_in_malloc_or_free()) {
+ return;
+ }
+
+ if (m_emulator.is_in_loader_code()) {
+ return;
+ }
+
+ auto* mallocation = find_mallocation(region, address);
+
+ if (!mallocation) {
+ reportln("\n=={}== \033[31;1mHeap buffer overflow\033[0m, invalid {}-byte read at address {:p}", getpid(), size, address);
+ m_emulator.dump_backtrace();
+ auto* mallocation_before = find_mallocation_before(address);
+ auto* mallocation_after = find_mallocation_after(address);
+ size_t distance_to_mallocation_before = mallocation_before ? (address - mallocation_before->address - mallocation_before->size) : 0;
+ size_t distance_to_mallocation_after = mallocation_after ? (mallocation_after->address - address) : 0;
+ if (mallocation_before && (!mallocation_after || distance_to_mallocation_before < distance_to_mallocation_after)) {
+ reportln("=={}== Address is {} byte(s) after block of size {}, identity {:p}, allocated at:", getpid(), distance_to_mallocation_before, mallocation_before->size, mallocation_before->address);
+ m_emulator.dump_backtrace(mallocation_before->malloc_backtrace);
+ return;
+ }
+ if (mallocation_after && (!mallocation_before || distance_to_mallocation_after < distance_to_mallocation_before)) {
+ reportln("=={}== Address is {} byte(s) before block of size {}, identity {:p}, allocated at:", getpid(), distance_to_mallocation_after, mallocation_after->size, mallocation_after->address);
+ m_emulator.dump_backtrace(mallocation_after->malloc_backtrace);
+ }
+ return;
+ }
+
+ size_t offset_into_mallocation = address - mallocation->address;
+
+ if (mallocation->freed) {
+ reportln("\n=={}== \033[31;1mUse-after-free\033[0m, invalid {}-byte read at address {:p}", getpid(), size, address);
+ m_emulator.dump_backtrace();
+ reportln("=={}== Address is {} byte(s) into block of size {}, allocated at:", getpid(), offset_into_mallocation, mallocation->size);
+ m_emulator.dump_backtrace(mallocation->malloc_backtrace);
+ reportln("=={}== Later freed at:", getpid());
+ m_emulator.dump_backtrace(mallocation->free_backtrace);
+ return;
+ }
+}
+
+void MallocTracer::audit_write(const Region& region, FlatPtr address, size_t size)
+{
+ if (!m_auditing_enabled)
+ return;
+
+ if (m_emulator.is_in_malloc_or_free())
+ return;
+
+ if (m_emulator.is_in_loader_code()) {
+ return;
+ }
+
+ auto* mallocation = find_mallocation(region, address);
+ if (!mallocation) {
+ reportln("\n=={}== \033[31;1mHeap buffer overflow\033[0m, invalid {}-byte write at address {:p}", getpid(), size, address);
+ m_emulator.dump_backtrace();
+ auto* mallocation_before = find_mallocation_before(address);
+ auto* mallocation_after = find_mallocation_after(address);
+ size_t distance_to_mallocation_before = mallocation_before ? (address - mallocation_before->address - mallocation_before->size) : 0;
+ size_t distance_to_mallocation_after = mallocation_after ? (mallocation_after->address - address) : 0;
+ if (mallocation_before && (!mallocation_after || distance_to_mallocation_before < distance_to_mallocation_after)) {
+ reportln("=={}== Address is {} byte(s) after block of size {}, identity {:p}, allocated at:", getpid(), distance_to_mallocation_before, mallocation_before->size, mallocation_before->address);
+ m_emulator.dump_backtrace(mallocation_before->malloc_backtrace);
+ return;
+ }
+ if (mallocation_after && (!mallocation_before || distance_to_mallocation_after < distance_to_mallocation_before)) {
+ reportln("=={}== Address is {} byte(s) before block of size {}, identity {:p}, allocated at:", getpid(), distance_to_mallocation_after, mallocation_after->size, mallocation_after->address);
+ m_emulator.dump_backtrace(mallocation_after->malloc_backtrace);
+ }
+ return;
+ }
+
+ size_t offset_into_mallocation = address - mallocation->address;
+
+ if (mallocation->freed) {
+ reportln("\n=={}== \033[31;1mUse-after-free\033[0m, invalid {}-byte write at address {:p}", getpid(), size, address);
+ m_emulator.dump_backtrace();
+ reportln("=={}== Address is {} byte(s) into block of size {}, allocated at:", getpid(), offset_into_mallocation, mallocation->size);
+ m_emulator.dump_backtrace(mallocation->malloc_backtrace);
+ reportln("=={}== Later freed at:", getpid());
+ m_emulator.dump_backtrace(mallocation->free_backtrace);
+ return;
+ }
+}
+
+bool MallocTracer::is_reachable(const Mallocation& mallocation) const
+{
+ ASSERT(!mallocation.freed);
+
+ bool reachable = false;
+
+ // 1. Search in active (non-freed) mallocations for pointers to this mallocation
+ for_each_mallocation([&](auto& other_mallocation) {
+ if (&mallocation == &other_mallocation)
+ return IterationDecision::Continue;
+ if (other_mallocation.freed)
+ return IterationDecision::Continue;
+ size_t pointers_in_mallocation = other_mallocation.size / sizeof(u32);
+ for (size_t i = 0; i < pointers_in_mallocation; ++i) {
+ auto value = m_emulator.mmu().read32({ 0x23, other_mallocation.address + i * sizeof(u32) });
+ if (value.value() == mallocation.address && !value.is_uninitialized()) {
+#ifdef REACHABLE_DEBUG
+ reportln("mallocation {:p} is reachable from other mallocation {:p}", mallocation.address, other_mallocation.address);
+#endif
+ reachable = true;
+ return IterationDecision::Break;
+ }
+ }
+ return IterationDecision::Continue;
+ });
+
+ if (reachable)
+ return true;
+
+ // 2. Search in other memory regions for pointers to this mallocation
+ m_emulator.mmu().for_each_region([&](auto& region) {
+ // Skip the stack
+ if (region.is_stack())
+ return IterationDecision::Continue;
+ if (region.is_text())
+ return IterationDecision::Continue;
+ if (!region.is_readable())
+ return IterationDecision::Continue;
+ // Skip malloc blocks
+ if (is<MmapRegion>(region) && static_cast<const MmapRegion&>(region).is_malloc_block())
+ return IterationDecision::Continue;
+
+ size_t pointers_in_region = region.size() / sizeof(u32);
+ for (size_t i = 0; i < pointers_in_region; ++i) {
+ auto value = region.read32(i * sizeof(u32));
+ if (value.value() == mallocation.address && !value.is_uninitialized()) {
+#ifdef REACHABLE_DEBUG
+ reportln("mallocation {:p} is reachable from region {:p}-{:p}", mallocation.address, region.base(), region.end() - 1);
+#endif
+ reachable = true;
+ return IterationDecision::Break;
+ }
+ }
+ return IterationDecision::Continue;
+ });
+ return reachable;
+}
+
+void MallocTracer::dump_leak_report()
+{
+ TemporaryChange change(m_auditing_enabled, false);
+
+ size_t bytes_leaked = 0;
+ size_t leaks_found = 0;
+ for_each_mallocation([&](auto& mallocation) {
+ if (mallocation.freed)
+ return IterationDecision::Continue;
+ if (is_reachable(mallocation))
+ return IterationDecision::Continue;
+ ++leaks_found;
+ bytes_leaked += mallocation.size;
+ reportln("\n=={}== \033[31;1mLeak\033[0m, {}-byte allocation at address {:p}", getpid(), mallocation.size, mallocation.address);
+ m_emulator.dump_backtrace(mallocation.malloc_backtrace);
+ return IterationDecision::Continue;
+ });
+
+ if (!leaks_found)
+ reportln("\n=={}== \033[32;1mNo leaks found!\033[0m", getpid());
+ else
+ reportln("\n=={}== \033[31;1m{} leak(s) found: {} byte(s) leaked\033[0m", getpid(), leaks_found, bytes_leaked);
+}
+}
diff --git a/Userland/DevTools/UserspaceEmulator/MallocTracer.h b/Userland/DevTools/UserspaceEmulator/MallocTracer.h
new file mode 100644
index 0000000000..53da97acf1
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/MallocTracer.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "MmapRegion.h"
+#include "SoftMMU.h"
+#include <AK/Badge.h>
+#include <AK/HashMap.h>
+#include <AK/OwnPtr.h>
+#include <AK/Types.h>
+#include <AK/Vector.h>
+
+namespace UserspaceEmulator {
+
+class Emulator;
+class SoftCPU;
+
+struct Mallocation {
+ bool contains(FlatPtr a) const
+ {
+ return a >= address && a < (address + size);
+ }
+
+ FlatPtr address { 0 };
+ size_t size { 0 };
+ bool used { false };
+ bool freed { false };
+
+ Vector<FlatPtr> malloc_backtrace;
+ Vector<FlatPtr> free_backtrace;
+};
+
+class MallocRegionMetadata {
+public:
+ FlatPtr address { 0 };
+ size_t chunk_size { 0 };
+
+ size_t chunk_index_for_address(FlatPtr) const;
+ Mallocation& mallocation_for_address(FlatPtr) const;
+
+ Vector<Mallocation> mallocations;
+};
+
+class MallocTracer {
+public:
+ explicit MallocTracer(Emulator&);
+
+ void target_did_malloc(Badge<SoftCPU>, FlatPtr address, size_t);
+ void target_did_free(Badge<SoftCPU>, FlatPtr address);
+ void target_did_realloc(Badge<SoftCPU>, FlatPtr address, size_t);
+
+ void audit_read(const Region&, FlatPtr address, size_t);
+ void audit_write(const Region&, FlatPtr address, size_t);
+
+ void dump_leak_report();
+
+private:
+ template<typename Callback>
+ void for_each_mallocation(Callback callback) const;
+
+ Mallocation* find_mallocation(const Region&, FlatPtr);
+ Mallocation* find_mallocation(FlatPtr);
+ Mallocation* find_mallocation_before(FlatPtr);
+ Mallocation* find_mallocation_after(FlatPtr);
+ bool is_reachable(const Mallocation&) const;
+
+ Emulator& m_emulator;
+
+ bool m_auditing_enabled { true };
+};
+
+ALWAYS_INLINE Mallocation* MallocTracer::find_mallocation(const Region& region, FlatPtr address)
+{
+ if (!is<MmapRegion>(region))
+ return nullptr;
+ if (!static_cast<const MmapRegion&>(region).is_malloc_block())
+ return nullptr;
+ auto* malloc_data = static_cast<MmapRegion&>(const_cast<Region&>(region)).malloc_metadata();
+ if (!malloc_data)
+ return nullptr;
+ auto& mallocation = malloc_data->mallocation_for_address(address);
+ if (!mallocation.used)
+ return nullptr;
+ ASSERT(mallocation.contains(address));
+ return &mallocation;
+}
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/MmapRegion.cpp b/Userland/DevTools/UserspaceEmulator/MmapRegion.cpp
new file mode 100644
index 0000000000..9e5eda4e56
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/MmapRegion.cpp
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "MmapRegion.h"
+#include "Emulator.h"
+#include <string.h>
+#include <sys/mman.h>
+
+namespace UserspaceEmulator {
+
+NonnullOwnPtr<MmapRegion> MmapRegion::create_anonymous(u32 base, u32 size, u32 prot)
+{
+ auto region = adopt_own(*new MmapRegion(base, size, prot));
+ region->m_file_backed = false;
+ region->m_data = (u8*)calloc(1, size);
+ return region;
+}
+
+NonnullOwnPtr<MmapRegion> MmapRegion::create_file_backed(u32 base, u32 size, u32 prot, int flags, int fd, off_t offset, String name)
+{
+ auto region = adopt_own(*new MmapRegion(base, size, prot));
+ region->m_file_backed = true;
+ if (!name.is_empty()) {
+ name = String::format("%s (Emulated)", name.characters());
+ region->m_name = name;
+ }
+ region->m_data = (u8*)mmap_with_name(nullptr, size, prot, flags, fd, offset, name.is_empty() ? nullptr : name.characters());
+ ASSERT(region->m_data != MAP_FAILED);
+ return region;
+}
+
+MmapRegion::MmapRegion(u32 base, u32 size, int prot)
+ : Region(base, size)
+{
+ set_prot(prot);
+ m_shadow_data = (u8*)malloc(size);
+ memset(m_shadow_data, 1, size);
+}
+
+MmapRegion::~MmapRegion()
+{
+ free(m_shadow_data);
+ if (m_file_backed)
+ munmap(m_data, size());
+ else
+ free(m_data);
+}
+
+ValueWithShadow<u8> MmapRegion::read8(FlatPtr offset)
+{
+ if (!is_readable()) {
+ reportln("8-bit read from unreadable MmapRegion @ {:p}", base() + offset);
+ emulator().dump_backtrace();
+ TODO();
+ }
+
+ if (is_malloc_block()) {
+ if (auto* tracer = emulator().malloc_tracer())
+ tracer->audit_read(*this, base() + offset, 1);
+ }
+
+ ASSERT(offset < size());
+ return { *reinterpret_cast<const u8*>(m_data + offset), *reinterpret_cast<const u8*>(m_shadow_data + offset) };
+}
+
+ValueWithShadow<u16> MmapRegion::read16(u32 offset)
+{
+ if (!is_readable()) {
+ reportln("16-bit read from unreadable MmapRegion @ {:p}", base() + offset);
+ emulator().dump_backtrace();
+ TODO();
+ }
+
+ if (is_malloc_block()) {
+ if (auto* tracer = emulator().malloc_tracer())
+ tracer->audit_read(*this, base() + offset, 2);
+ }
+
+ ASSERT(offset + 1 < size());
+ return { *reinterpret_cast<const u16*>(m_data + offset), *reinterpret_cast<const u16*>(m_shadow_data + offset) };
+}
+
+ValueWithShadow<u32> MmapRegion::read32(u32 offset)
+{
+ if (!is_readable()) {
+ reportln("32-bit read from unreadable MmapRegion @ {:p}", base() + offset);
+ emulator().dump_backtrace();
+ TODO();
+ }
+
+ if (is_malloc_block()) {
+ if (auto* tracer = emulator().malloc_tracer())
+ tracer->audit_read(*this, base() + offset, 4);
+ }
+
+ ASSERT(offset + 3 < size());
+ return { *reinterpret_cast<const u32*>(m_data + offset), *reinterpret_cast<const u32*>(m_shadow_data + offset) };
+}
+
+ValueWithShadow<u64> MmapRegion::read64(u32 offset)
+{
+ if (!is_readable()) {
+ reportln("64-bit read from unreadable MmapRegion @ {:p}", base() + offset);
+ emulator().dump_backtrace();
+ TODO();
+ }
+
+ if (is_malloc_block()) {
+ if (auto* tracer = emulator().malloc_tracer())
+ tracer->audit_read(*this, base() + offset, 8);
+ }
+
+ ASSERT(offset + 7 < size());
+ return { *reinterpret_cast<const u64*>(m_data + offset), *reinterpret_cast<const u64*>(m_shadow_data + offset) };
+}
+
+void MmapRegion::write8(u32 offset, ValueWithShadow<u8> value)
+{
+ if (!is_writable()) {
+ reportln("8-bit write from unwritable MmapRegion @ {:p}", base() + offset);
+ emulator().dump_backtrace();
+ TODO();
+ }
+
+ if (is_malloc_block()) {
+ if (auto* tracer = emulator().malloc_tracer())
+ tracer->audit_write(*this, base() + offset, 1);
+ }
+
+ ASSERT(offset < size());
+ *reinterpret_cast<u8*>(m_data + offset) = value.value();
+ *reinterpret_cast<u8*>(m_shadow_data + offset) = value.shadow();
+}
+
+void MmapRegion::write16(u32 offset, ValueWithShadow<u16> value)
+{
+ if (!is_writable()) {
+ reportln("16-bit write from unwritable MmapRegion @ {:p}", base() + offset);
+ emulator().dump_backtrace();
+ TODO();
+ }
+
+ if (is_malloc_block()) {
+ if (auto* tracer = emulator().malloc_tracer())
+ tracer->audit_write(*this, base() + offset, 2);
+ }
+
+ ASSERT(offset + 1 < size());
+ *reinterpret_cast<u16*>(m_data + offset) = value.value();
+ *reinterpret_cast<u16*>(m_shadow_data + offset) = value.shadow();
+}
+
+void MmapRegion::write32(u32 offset, ValueWithShadow<u32> value)
+{
+ if (!is_writable()) {
+ reportln("32-bit write from unwritable MmapRegion @ {:p}", base() + offset);
+ emulator().dump_backtrace();
+ TODO();
+ }
+
+ if (is_malloc_block()) {
+ if (auto* tracer = emulator().malloc_tracer())
+ tracer->audit_write(*this, base() + offset, 4);
+ }
+
+ ASSERT(offset + 3 < size());
+ ASSERT(m_data != m_shadow_data);
+ *reinterpret_cast<u32*>(m_data + offset) = value.value();
+ *reinterpret_cast<u32*>(m_shadow_data + offset) = value.shadow();
+}
+
+void MmapRegion::write64(u32 offset, ValueWithShadow<u64> value)
+{
+ if (!is_writable()) {
+ reportln("64-bit write from unwritable MmapRegion @ {:p}", base() + offset);
+ emulator().dump_backtrace();
+ TODO();
+ }
+
+ if (is_malloc_block()) {
+ if (auto* tracer = emulator().malloc_tracer())
+ tracer->audit_write(*this, base() + offset, 8);
+ }
+
+ ASSERT(offset + 7 < size());
+ ASSERT(m_data != m_shadow_data);
+ *reinterpret_cast<u64*>(m_data + offset) = value.value();
+ *reinterpret_cast<u64*>(m_shadow_data + offset) = value.shadow();
+}
+
+void MmapRegion::set_prot(int prot)
+{
+ set_readable(prot & PROT_READ);
+ set_writable(prot & PROT_WRITE);
+ set_executable(prot & PROT_EXEC);
+ if (m_file_backed) {
+ mprotect(m_data, size(), prot);
+ }
+}
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/MmapRegion.h b/Userland/DevTools/UserspaceEmulator/MmapRegion.h
new file mode 100644
index 0000000000..75a42340ab
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/MmapRegion.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "SoftMMU.h"
+#include <sys/mman.h>
+
+namespace UserspaceEmulator {
+
+class MallocRegionMetadata;
+class MallocTracer;
+
+class MmapRegion final : public Region {
+public:
+ static NonnullOwnPtr<MmapRegion> create_anonymous(u32 base, u32 size, u32 prot);
+ static NonnullOwnPtr<MmapRegion> create_file_backed(u32 base, u32 size, u32 prot, int flags, int fd, off_t offset, String name = {});
+ virtual ~MmapRegion() override;
+
+ virtual ValueWithShadow<u8> read8(u32 offset) override;
+ virtual ValueWithShadow<u16> read16(u32 offset) override;
+ virtual ValueWithShadow<u32> read32(u32 offset) override;
+ virtual ValueWithShadow<u64> read64(u32 offset) override;
+
+ virtual void write8(u32 offset, ValueWithShadow<u8>) override;
+ virtual void write16(u32 offset, ValueWithShadow<u16>) override;
+ virtual void write32(u32 offset, ValueWithShadow<u32>) override;
+ virtual void write64(u32 offset, ValueWithShadow<u64>) override;
+
+ virtual u8* data() override { return m_data; }
+ virtual u8* shadow_data() override { return m_shadow_data; }
+
+ bool is_malloc_block() const { return m_malloc; }
+ void set_malloc(bool b) { m_malloc = b; }
+
+ void set_prot(int prot);
+
+ MallocRegionMetadata* malloc_metadata() { return m_malloc_metadata; }
+ void set_malloc_metadata(Badge<MallocTracer>, NonnullOwnPtr<MallocRegionMetadata> metadata) { m_malloc_metadata = move(metadata); }
+
+ const String& name() const { return m_name; }
+
+private:
+ MmapRegion(u32 base, u32 size, int prot);
+
+ u8* m_data { nullptr };
+ u8* m_shadow_data { nullptr };
+ bool m_file_backed { false };
+ bool m_malloc { false };
+
+ OwnPtr<MallocRegionMetadata> m_malloc_metadata;
+ String m_name;
+};
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/Region.cpp b/Userland/DevTools/UserspaceEmulator/Region.cpp
new file mode 100644
index 0000000000..c5f989c802
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/Region.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Region.h"
+#include "Emulator.h"
+
+namespace UserspaceEmulator {
+
+Region::Region(u32 base, u32 size)
+ : m_emulator(Emulator::the())
+ , m_base(base)
+ , m_size(size)
+{
+}
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/Region.h b/Userland/DevTools/UserspaceEmulator/Region.h
new file mode 100644
index 0000000000..ffb15cf941
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/Region.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "ValueWithShadow.h"
+#include <AK/TypeCasts.h>
+#include <AK/Types.h>
+
+namespace UserspaceEmulator {
+
+class Emulator;
+
+class Region {
+public:
+ virtual ~Region() { }
+
+ u32 base() const { return m_base; }
+ u32 size() const { return m_size; }
+ u32 end() const { return m_base + m_size; }
+
+ bool contains(u32 address) const { return address >= base() && address < end(); }
+
+ virtual void write8(u32 offset, ValueWithShadow<u8>) = 0;
+ virtual void write16(u32 offset, ValueWithShadow<u16>) = 0;
+ virtual void write32(u32 offset, ValueWithShadow<u32>) = 0;
+ virtual void write64(u32 offset, ValueWithShadow<u64>) = 0;
+
+ virtual ValueWithShadow<u8> read8(u32 offset) = 0;
+ virtual ValueWithShadow<u16> read16(u32 offset) = 0;
+ virtual ValueWithShadow<u32> read32(u32 offset) = 0;
+ virtual ValueWithShadow<u64> read64(u32 offset) = 0;
+
+ virtual u8* cacheable_ptr([[maybe_unused]] u32 offset) { return nullptr; }
+
+ bool is_stack() const { return m_stack; }
+ void set_stack(bool b) { m_stack = b; }
+
+ bool is_text() const { return m_text; }
+ void set_text(bool b) { m_text = b; }
+
+ bool is_readable() const { return m_readable; }
+ bool is_writable() const { return m_writable; }
+ bool is_executable() const { return m_executable; }
+
+ void set_readable(bool b) { m_readable = b; }
+ void set_writable(bool b) { m_writable = b; }
+ void set_executable(bool b) { m_executable = b; }
+
+ virtual u8* data() = 0;
+ virtual u8* shadow_data() = 0;
+
+ Emulator& emulator() { return m_emulator; }
+ const Emulator& emulator() const { return m_emulator; }
+
+protected:
+ Region(u32 base, u32 size);
+
+private:
+ Emulator& m_emulator;
+
+ u32 m_base { 0 };
+ u32 m_size { 0 };
+
+ bool m_stack { false };
+ bool m_text { false };
+ bool m_readable { true };
+ bool m_writable { true };
+ bool m_executable { true };
+};
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/Report.h b/Userland/DevTools/UserspaceEmulator/Report.h
new file mode 100644
index 0000000000..97dcf97537
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/Report.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/LogStream.h>
+
+extern bool g_report_to_debug;
+
+template<typename... Ts>
+void reportln(const StringView& format, Ts... args)
+{
+ if (g_report_to_debug)
+ dbgln(format, args...);
+ else
+ warnln(format, args...);
+}
diff --git a/Userland/DevTools/UserspaceEmulator/SharedBufferRegion.cpp b/Userland/DevTools/UserspaceEmulator/SharedBufferRegion.cpp
new file mode 100644
index 0000000000..aec26c2082
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/SharedBufferRegion.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "SharedBufferRegion.h"
+#include "Emulator.h"
+#include <Kernel/API/Syscall.h>
+#include <serenity.h>
+#include <string.h>
+#include <sys/mman.h>
+
+namespace UserspaceEmulator {
+
+NonnullOwnPtr<SharedBufferRegion> SharedBufferRegion::create_with_shbuf_id(u32 base, u32 size, int shbuf_id, u8* host_data)
+{
+ return adopt_own(*new SharedBufferRegion(base, size, shbuf_id, host_data));
+}
+
+SharedBufferRegion::SharedBufferRegion(u32 base, u32 size, int shbuf_id, u8* host_data)
+ : Region(base, size)
+ , m_data(host_data)
+ , m_shbuf_id(shbuf_id)
+{
+ m_shadow_data = (u8*)malloc(size);
+ memset(m_shadow_data, 1, size);
+}
+
+SharedBufferRegion::~SharedBufferRegion()
+{
+ free(m_shadow_data);
+}
+
+ValueWithShadow<u8> SharedBufferRegion::read8(FlatPtr offset)
+{
+ ASSERT(offset < size());
+ return { *reinterpret_cast<const u8*>(m_data + offset), *reinterpret_cast<const u8*>(m_shadow_data + offset) };
+}
+
+ValueWithShadow<u16> SharedBufferRegion::read16(u32 offset)
+{
+ ASSERT(offset + 1 < size());
+ return { *reinterpret_cast<const u16*>(m_data + offset), *reinterpret_cast<const u16*>(m_shadow_data + offset) };
+}
+
+ValueWithShadow<u32> SharedBufferRegion::read32(u32 offset)
+{
+ ASSERT(offset + 3 < size());
+ return { *reinterpret_cast<const u32*>(m_data + offset), *reinterpret_cast<const u32*>(m_shadow_data + offset) };
+}
+
+ValueWithShadow<u64> SharedBufferRegion::read64(u32 offset)
+{
+ ASSERT(offset + 7 < size());
+ return { *reinterpret_cast<const u64*>(m_data + offset), *reinterpret_cast<const u64*>(m_shadow_data + offset) };
+}
+
+void SharedBufferRegion::write8(u32 offset, ValueWithShadow<u8> value)
+{
+ ASSERT(offset < size());
+ *reinterpret_cast<u8*>(m_data + offset) = value.value();
+ *reinterpret_cast<u8*>(m_shadow_data + offset) = value.shadow();
+}
+
+void SharedBufferRegion::write16(u32 offset, ValueWithShadow<u16> value)
+{
+ ASSERT(offset + 1 < size());
+ *reinterpret_cast<u16*>(m_data + offset) = value.value();
+ *reinterpret_cast<u16*>(m_shadow_data + offset) = value.shadow();
+}
+
+void SharedBufferRegion::write32(u32 offset, ValueWithShadow<u32> value)
+{
+ ASSERT(offset + 3 < size());
+ *reinterpret_cast<u32*>(m_data + offset) = value.value();
+ *reinterpret_cast<u32*>(m_shadow_data + offset) = value.shadow();
+}
+
+void SharedBufferRegion::write64(u32 offset, ValueWithShadow<u64> value)
+{
+ ASSERT(offset + 7 < size());
+ *reinterpret_cast<u64*>(m_data + offset) = value.value();
+ *reinterpret_cast<u64*>(m_shadow_data + offset) = value.shadow();
+}
+
+int SharedBufferRegion::allow_all()
+{
+ return syscall(SC_shbuf_allow_all, m_shbuf_id);
+}
+
+int SharedBufferRegion::allow_pid(pid_t pid)
+{
+ return syscall(SC_shbuf_allow_pid, m_shbuf_id, pid);
+}
+
+int SharedBufferRegion::seal()
+{
+ return syscall(SC_shbuf_seal, m_shbuf_id);
+}
+
+int SharedBufferRegion::release()
+{
+ return syscall(SC_shbuf_release, m_shbuf_id);
+}
+
+int SharedBufferRegion::set_volatile(bool is_volatile)
+{
+ return syscall(SC_shbuf_set_volatile, m_shbuf_id, is_volatile);
+}
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/SharedBufferRegion.h b/Userland/DevTools/UserspaceEmulator/SharedBufferRegion.h
new file mode 100644
index 0000000000..b3c7d68d5c
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/SharedBufferRegion.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "SoftMMU.h"
+#include <sys/mman.h>
+
+namespace UserspaceEmulator {
+
+class SharedBufferRegion final : public Region {
+public:
+ static NonnullOwnPtr<SharedBufferRegion> create_with_shbuf_id(u32 base, u32 size, int shbuf_id, u8* shbuf_data);
+ virtual ~SharedBufferRegion() override;
+
+ virtual ValueWithShadow<u8> read8(u32 offset) override;
+ virtual ValueWithShadow<u16> read16(u32 offset) override;
+ virtual ValueWithShadow<u32> read32(u32 offset) override;
+ virtual ValueWithShadow<u64> read64(u32 offset) override;
+
+ virtual void write8(u32 offset, ValueWithShadow<u8>) override;
+ virtual void write16(u32 offset, ValueWithShadow<u16>) override;
+ virtual void write32(u32 offset, ValueWithShadow<u32>) override;
+ virtual void write64(u32 offset, ValueWithShadow<u64>) override;
+
+ virtual u8* data() override { return m_data; }
+ virtual u8* shadow_data() override { return m_shadow_data; }
+
+ int shbuf_id() const { return m_shbuf_id; }
+
+ int allow_all();
+ int allow_pid(pid_t);
+ int seal();
+ int release();
+ int set_volatile(bool);
+
+private:
+ SharedBufferRegion(u32 base, u32 size, int shbuf_id, u8* shbuf_data);
+
+ u8* m_data { nullptr };
+ u8* m_shadow_data { nullptr };
+ int m_shbuf_id { 0 };
+};
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/SimpleRegion.cpp b/Userland/DevTools/UserspaceEmulator/SimpleRegion.cpp
new file mode 100644
index 0000000000..173efd0e32
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/SimpleRegion.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "SimpleRegion.h"
+#include <string.h>
+
+namespace UserspaceEmulator {
+
+SimpleRegion::SimpleRegion(u32 base, u32 size)
+ : Region(base, size)
+{
+ m_data = (u8*)calloc(1, size);
+ m_shadow_data = (u8*)malloc(size);
+ memset(m_shadow_data, 1, size);
+}
+
+SimpleRegion::~SimpleRegion()
+{
+ free(m_shadow_data);
+ free(m_data);
+}
+
+ValueWithShadow<u8> SimpleRegion::read8(FlatPtr offset)
+{
+ ASSERT(offset < size());
+ return { *reinterpret_cast<const u8*>(m_data + offset), *reinterpret_cast<const u8*>(m_shadow_data + offset) };
+}
+
+ValueWithShadow<u16> SimpleRegion::read16(u32 offset)
+{
+ ASSERT(offset + 1 < size());
+ return { *reinterpret_cast<const u16*>(m_data + offset), *reinterpret_cast<const u16*>(m_shadow_data + offset) };
+}
+
+ValueWithShadow<u32> SimpleRegion::read32(u32 offset)
+{
+ ASSERT(offset + 3 < size());
+ return { *reinterpret_cast<const u32*>(m_data + offset), *reinterpret_cast<const u32*>(m_shadow_data + offset) };
+}
+
+ValueWithShadow<u64> SimpleRegion::read64(u32 offset)
+{
+ ASSERT(offset + 7 < size());
+ return { *reinterpret_cast<const u64*>(m_data + offset), *reinterpret_cast<const u64*>(m_shadow_data + offset) };
+}
+
+void SimpleRegion::write8(u32 offset, ValueWithShadow<u8> value)
+{
+ ASSERT(offset < size());
+ *reinterpret_cast<u8*>(m_data + offset) = value.value();
+ *reinterpret_cast<u8*>(m_shadow_data + offset) = value.shadow();
+}
+
+void SimpleRegion::write16(u32 offset, ValueWithShadow<u16> value)
+{
+ ASSERT(offset + 1 < size());
+ *reinterpret_cast<u16*>(m_data + offset) = value.value();
+ *reinterpret_cast<u16*>(m_shadow_data + offset) = value.shadow();
+}
+
+void SimpleRegion::write32(u32 offset, ValueWithShadow<u32> value)
+{
+ ASSERT(offset + 3 < size());
+ *reinterpret_cast<u32*>(m_data + offset) = value.value();
+ *reinterpret_cast<u32*>(m_shadow_data + offset) = value.shadow();
+}
+
+void SimpleRegion::write64(u32 offset, ValueWithShadow<u64> value)
+{
+ ASSERT(offset + 7 < size());
+ *reinterpret_cast<u64*>(m_data + offset) = value.value();
+ *reinterpret_cast<u64*>(m_shadow_data + offset) = value.shadow();
+}
+
+u8* SimpleRegion::cacheable_ptr(u32 offset)
+{
+ return m_data + offset;
+}
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/SimpleRegion.h b/Userland/DevTools/UserspaceEmulator/SimpleRegion.h
new file mode 100644
index 0000000000..202f283741
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/SimpleRegion.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "SoftMMU.h"
+
+namespace UserspaceEmulator {
+
+class SimpleRegion final : public Region {
+public:
+ SimpleRegion(u32 base, u32 size);
+ virtual ~SimpleRegion() override;
+
+ virtual ValueWithShadow<u8> read8(u32 offset) override;
+ virtual ValueWithShadow<u16> read16(u32 offset) override;
+ virtual ValueWithShadow<u32> read32(u32 offset) override;
+ virtual ValueWithShadow<u64> read64(u32 offset) override;
+
+ virtual void write8(u32 offset, ValueWithShadow<u8>) override;
+ virtual void write16(u32 offset, ValueWithShadow<u16>) override;
+ virtual void write32(u32 offset, ValueWithShadow<u32>) override;
+ virtual void write64(u32 offset, ValueWithShadow<u64>) override;
+
+ virtual u8* data() override { return m_data; }
+ virtual u8* shadow_data() override { return m_shadow_data; }
+
+ virtual u8* cacheable_ptr(u32 offset) override;
+
+private:
+ u8* m_data { nullptr };
+ u8* m_shadow_data { nullptr };
+};
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/SoftCPU.cpp b/Userland/DevTools/UserspaceEmulator/SoftCPU.cpp
new file mode 100644
index 0000000000..add134037f
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/SoftCPU.cpp
@@ -0,0 +1,3282 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "SoftCPU.h"
+#include "Emulator.h"
+#include <AK/Assertions.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC optimize("O3")
+#endif
+
+#define TODO_INSN() \
+ do { \
+ reportln("\n=={}== Unimplemented instruction: {}\n", getpid(), __FUNCTION__); \
+ m_emulator.dump_backtrace(); \
+ _exit(0); \
+ } while (0)
+
+//#define MEMORY_DEBUG
+
+#define DEFINE_GENERIC_SHIFT_ROTATE_INSN_HANDLERS(mnemonic, op) \
+ void SoftCPU::mnemonic##_RM8_1(const X86::Instruction& insn) { generic_RM8_1(op<ValueWithShadow<u8>>, insn); } \
+ void SoftCPU::mnemonic##_RM8_CL(const X86::Instruction& insn) { generic_RM8_CL(op<ValueWithShadow<u8>>, insn); } \
+ void SoftCPU::mnemonic##_RM8_imm8(const X86::Instruction& insn) { generic_RM8_imm8<true, false>(op<ValueWithShadow<u8>>, insn); } \
+ void SoftCPU::mnemonic##_RM16_1(const X86::Instruction& insn) { generic_RM16_1(op<ValueWithShadow<u16>>, insn); } \
+ void SoftCPU::mnemonic##_RM16_CL(const X86::Instruction& insn) { generic_RM16_CL(op<ValueWithShadow<u16>>, insn); } \
+ void SoftCPU::mnemonic##_RM16_imm8(const X86::Instruction& insn) { generic_RM16_unsigned_imm8<true>(op<ValueWithShadow<u16>>, insn); } \
+ void SoftCPU::mnemonic##_RM32_1(const X86::Instruction& insn) { generic_RM32_1(op<ValueWithShadow<u32>>, insn); } \
+ void SoftCPU::mnemonic##_RM32_CL(const X86::Instruction& insn) { generic_RM32_CL(op<ValueWithShadow<u32>>, insn); } \
+ void SoftCPU::mnemonic##_RM32_imm8(const X86::Instruction& insn) { generic_RM32_unsigned_imm8<true>(op<ValueWithShadow<u32>>, insn); }
+
+namespace UserspaceEmulator {
+
+template<class Dest, class Source>
+static inline Dest bit_cast(Source source)
+{
+ static_assert(sizeof(Dest) == sizeof(Source));
+ Dest dest;
+ memcpy(&dest, &source, sizeof(dest));
+ return dest;
+}
+
+template<typename T>
+void warn_if_uninitialized(T value_with_shadow, const char* message)
+{
+ if (value_with_shadow.is_uninitialized()) {
+ reportln("\033[31;1mWarning! Use of uninitialized value: {}\033[0m\n", message);
+ Emulator::the().dump_backtrace();
+ }
+}
+
+void SoftCPU::warn_if_flags_tainted(const char* message) const
+{
+ if (m_flags_tainted) {
+ reportln("\n=={}== \033[31;1mConditional depends on uninitialized data\033[0m ({})\n", getpid(), message);
+ Emulator::the().dump_backtrace();
+ }
+}
+
+template<typename T, typename U>
+constexpr T sign_extended_to(U value)
+{
+ if (!(value & X86::TypeTrivia<U>::sign_bit))
+ return value;
+ return (X86::TypeTrivia<T>::mask & ~X86::TypeTrivia<U>::mask) | value;
+}
+
+SoftCPU::SoftCPU(Emulator& emulator)
+ : m_emulator(emulator)
+{
+ memset(m_gpr, 0, sizeof(m_gpr));
+ memset(m_gpr_shadow, 1, sizeof(m_gpr_shadow));
+
+ m_segment[(int)X86::SegmentRegister::CS] = 0x1b;
+ m_segment[(int)X86::SegmentRegister::DS] = 0x23;
+ m_segment[(int)X86::SegmentRegister::ES] = 0x23;
+ m_segment[(int)X86::SegmentRegister::SS] = 0x23;
+ m_segment[(int)X86::SegmentRegister::GS] = 0x2b;
+}
+
+void SoftCPU::dump() const
+{
+ outln(" eax={:08x} ebx={:08x} ecx={:08x} edx={:08x} ebp={:08x} esp={:08x} esi={:08x} edi={:08x} o={:d} s={:d} z={:d} a={:d} p={:d} c={:d}",
+ eax(), ebx(), ecx(), edx(), ebp(), esp(), esi(), edi(), of(), sf(), zf(), af(), pf(), cf());
+ outln("#eax={:08x} #ebx={:08x} #ecx={:08x} #edx={:08x} #ebp={:08x} #esp={:08x} #esi={:08x} #edi={:08x} #f={}",
+ eax().shadow(), ebx().shadow(), ecx().shadow(), edx().shadow(), m_flags_tainted);
+ fflush(stdout);
+}
+
+void SoftCPU::did_receive_secret_data()
+{
+ if (m_secret_data[0] == 1) {
+ if (auto* tracer = m_emulator.malloc_tracer())
+ tracer->target_did_malloc({}, m_secret_data[2], m_secret_data[1]);
+ } else if (m_secret_data[0] == 2) {
+ if (auto* tracer = m_emulator.malloc_tracer())
+ tracer->target_did_free({}, m_secret_data[1]);
+ } else if (m_secret_data[0] == 3) {
+ if (auto* tracer = m_emulator.malloc_tracer())
+ tracer->target_did_realloc({}, m_secret_data[2], m_secret_data[1]);
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+}
+
+void SoftCPU::update_code_cache()
+{
+ auto* region = m_emulator.mmu().find_region({ cs(), eip() });
+ ASSERT(region);
+
+ if (!region->is_executable()) {
+ reportln("SoftCPU::update_code_cache: Non-executable region @ {:p}", eip());
+ Emulator::the().dump_backtrace();
+ TODO();
+ }
+
+ // FIXME: This cache needs to be invalidated if the code region is ever unmapped.
+ m_cached_code_region = region;
+ m_cached_code_base_ptr = region->data();
+}
+
+ValueWithShadow<u8> SoftCPU::read_memory8(X86::LogicalAddress address)
+{
+ ASSERT(address.selector() == 0x1b || address.selector() == 0x23 || address.selector() == 0x2b);
+ auto value = m_emulator.mmu().read8(address);
+#ifdef MEMORY_DEBUG
+ outln("\033[36;1mread_memory8: @{:04x}:{:08x} -> {:02x} ({:02x})\033[0m", address.selector(), address.offset(), value, value.shadow());
+#endif
+ return value;
+}
+
+ValueWithShadow<u16> SoftCPU::read_memory16(X86::LogicalAddress address)
+{
+ ASSERT(address.selector() == 0x1b || address.selector() == 0x23 || address.selector() == 0x2b);
+ auto value = m_emulator.mmu().read16(address);
+#ifdef MEMORY_DEBUG
+ outln("\033[36;1mread_memory16: @{:04x}:{:08x} -> {:04x} ({:04x})\033[0m", address.selector(), address.offset(), value, value.shadow());
+#endif
+ return value;
+}
+
+ValueWithShadow<u32> SoftCPU::read_memory32(X86::LogicalAddress address)
+{
+ ASSERT(address.selector() == 0x1b || address.selector() == 0x23 || address.selector() == 0x2b);
+ auto value = m_emulator.mmu().read32(address);
+#ifdef MEMORY_DEBUG
+ outln("\033[36;1mread_memory32: @{:04x}:{:08x} -> {:08x} ({:08x})\033[0m", address.selector(), address.offset(), value, value.shadow());
+#endif
+ return value;
+}
+
+ValueWithShadow<u64> SoftCPU::read_memory64(X86::LogicalAddress address)
+{
+ ASSERT(address.selector() == 0x1b || address.selector() == 0x23 || address.selector() == 0x2b);
+ auto value = m_emulator.mmu().read64(address);
+#ifdef MEMORY_DEBUG
+ outln("\033[36;1mread_memory64: @{:04x}:{:08x} -> {:016x} ({:016x})\033[0m", address.selector(), address.offset(), value, value.shadow());
+#endif
+ return value;
+}
+
+void SoftCPU::write_memory8(X86::LogicalAddress address, ValueWithShadow<u8> value)
+{
+ ASSERT(address.selector() == 0x23 || address.selector() == 0x2b);
+#ifdef MEMORY_DEBUG
+ outln("\033[36;1mwrite_memory8: @{:04x}:{:08x} <- {:02x} ({:02x})\033[0m", address.selector(), address.offset(), value, value.shadow());
+#endif
+ m_emulator.mmu().write8(address, value);
+}
+
+void SoftCPU::write_memory16(X86::LogicalAddress address, ValueWithShadow<u16> value)
+{
+ ASSERT(address.selector() == 0x23 || address.selector() == 0x2b);
+#ifdef MEMORY_DEBUG
+ outln("\033[36;1mwrite_memory16: @{:04x}:{:08x} <- {:04x} ({:04x})\033[0m", address.selector(), address.offset(), value, value.shadow());
+#endif
+ m_emulator.mmu().write16(address, value);
+}
+
+void SoftCPU::write_memory32(X86::LogicalAddress address, ValueWithShadow<u32> value)
+{
+ ASSERT(address.selector() == 0x23 || address.selector() == 0x2b);
+#ifdef MEMORY_DEBUG
+ outln("\033[36;1mwrite_memory32: @{:04x}:{:08x} <- {:08x} ({:08x})\033[0m", address.selector(), address.offset(), value, value.shadow());
+#endif
+ m_emulator.mmu().write32(address, value);
+}
+
+void SoftCPU::write_memory64(X86::LogicalAddress address, ValueWithShadow<u64> value)
+{
+ ASSERT(address.selector() == 0x23 || address.selector() == 0x2b);
+#ifdef MEMORY_DEBUG
+ outln("\033[36;1mwrite_memory64: @{:04x}:{:08x} <- {:016x} ({:016x})\033[0m", address.selector(), address.offset(), value, value.shadow());
+#endif
+ m_emulator.mmu().write64(address, value);
+}
+
+void SoftCPU::push_string(const StringView& string)
+{
+ size_t space_to_allocate = round_up_to_power_of_two(string.length() + 1, 16);
+ set_esp({ esp().value() - space_to_allocate, esp().shadow() });
+ m_emulator.mmu().copy_to_vm(esp().value(), string.characters_without_null_termination(), string.length());
+ m_emulator.mmu().write8({ 0x23, esp().value() + string.length() }, shadow_wrap_as_initialized((u8)'\0'));
+}
+
+void SoftCPU::push_buffer(const u8* data, size_t size)
+{
+ set_esp({ esp().value() - size, esp().shadow() });
+ warn_if_uninitialized(esp(), "push_buffer");
+ m_emulator.mmu().copy_to_vm(esp().value(), data, size);
+}
+
+void SoftCPU::push32(ValueWithShadow<u32> value)
+{
+ set_esp({ esp().value() - sizeof(u32), esp().shadow() });
+ warn_if_uninitialized(esp(), "push32");
+ write_memory32({ ss(), esp().value() }, value);
+}
+
+ValueWithShadow<u32> SoftCPU::pop32()
+{
+ warn_if_uninitialized(esp(), "pop32");
+ auto value = read_memory32({ ss(), esp().value() });
+ set_esp({ esp().value() + sizeof(u32), esp().shadow() });
+ return value;
+}
+
+void SoftCPU::push16(ValueWithShadow<u16> value)
+{
+ warn_if_uninitialized(esp(), "push16");
+ set_esp({ esp().value() - sizeof(u16), esp().shadow() });
+ write_memory16({ ss(), esp().value() }, value);
+}
+
+ValueWithShadow<u16> SoftCPU::pop16()
+{
+ warn_if_uninitialized(esp(), "pop16");
+ auto value = read_memory16({ ss(), esp().value() });
+ set_esp({ esp().value() + sizeof(u16), esp().shadow() });
+ return value;
+}
+
+template<bool check_zf, typename Callback>
+void SoftCPU::do_once_or_repeat(const X86::Instruction& insn, Callback callback)
+{
+ if (!insn.has_rep_prefix())
+ return callback();
+
+ while (loop_index(insn.a32()).value()) {
+ callback();
+ decrement_loop_index(insn.a32());
+ if constexpr (check_zf) {
+ warn_if_flags_tainted("repz/repnz");
+ if (insn.rep_prefix() == X86::Prefix::REPZ && !zf())
+ break;
+ if (insn.rep_prefix() == X86::Prefix::REPNZ && zf())
+ break;
+ }
+ }
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_inc(SoftCPU& cpu, T data)
+{
+ typename T::ValueType result;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("incl %%eax\n"
+ : "=a"(result)
+ : "a"(data.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("incw %%ax\n"
+ : "=a"(result)
+ : "a"(data.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("incb %%al\n"
+ : "=a"(result)
+ : "a"(data.value()));
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszap(new_flags);
+ cpu.taint_flags_from(data);
+ return shadow_wrap_with_taint_from(result, data);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_dec(SoftCPU& cpu, T data)
+{
+ typename T::ValueType result;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("decl %%eax\n"
+ : "=a"(result)
+ : "a"(data.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("decw %%ax\n"
+ : "=a"(result)
+ : "a"(data.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("decb %%al\n"
+ : "=a"(result)
+ : "a"(data.value()));
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszap(new_flags);
+ cpu.taint_flags_from(data);
+ return shadow_wrap_with_taint_from(result, data);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_xor(SoftCPU& cpu, const T& dest, const T& src)
+{
+ typename T::ValueType result;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("xorl %%ecx, %%eax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("xor %%cx, %%ax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("xorb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszpc(new_flags);
+ cpu.taint_flags_from(dest, src);
+ return shadow_wrap_with_taint_from(result, dest, src);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_or(SoftCPU& cpu, const T& dest, const T& src)
+{
+ typename T::ValueType result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("orl %%ecx, %%eax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("or %%cx, %%ax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("orb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszpc(new_flags);
+ cpu.taint_flags_from(dest, src);
+ return shadow_wrap_with_taint_from(result, dest, src);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_sub(SoftCPU& cpu, const T& dest, const T& src)
+{
+ typename T::ValueType result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("subl %%ecx, %%eax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("subw %%cx, %%ax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("subb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszapc(new_flags);
+ cpu.taint_flags_from(dest, src);
+ return shadow_wrap_with_taint_from(result, dest, src);
+}
+
+template<typename T, bool cf>
+ALWAYS_INLINE static T op_sbb_impl(SoftCPU& cpu, const T& dest, const T& src)
+{
+ typename T::ValueType result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (cf)
+ asm volatile("stc");
+ else
+ asm volatile("clc");
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("sbbl %%ecx, %%eax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("sbbw %%cx, %%ax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("sbbb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszapc(new_flags);
+ cpu.taint_flags_from(dest, src);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, dest, src);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_sbb(SoftCPU& cpu, T& dest, const T& src)
+{
+ cpu.warn_if_flags_tainted("sbb");
+ if (cpu.cf())
+ return op_sbb_impl<T, true>(cpu, dest, src);
+ return op_sbb_impl<T, false>(cpu, dest, src);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_add(SoftCPU& cpu, T& dest, const T& src)
+{
+ typename T::ValueType result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("addl %%ecx, %%eax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("addw %%cx, %%ax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("addb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszapc(new_flags);
+ cpu.taint_flags_from(dest, src);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, dest, src);
+}
+
+template<typename T, bool cf>
+ALWAYS_INLINE static T op_adc_impl(SoftCPU& cpu, T& dest, const T& src)
+{
+ typename T::ValueType result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (cf)
+ asm volatile("stc");
+ else
+ asm volatile("clc");
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("adcl %%ecx, %%eax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("adcw %%cx, %%ax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("adcb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszapc(new_flags);
+ cpu.taint_flags_from(dest, src);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, dest, src);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_adc(SoftCPU& cpu, T& dest, const T& src)
+{
+ cpu.warn_if_flags_tainted("adc");
+ if (cpu.cf())
+ return op_adc_impl<T, true>(cpu, dest, src);
+ return op_adc_impl<T, false>(cpu, dest, src);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_and(SoftCPU& cpu, const T& dest, const T& src)
+{
+ typename T::ValueType result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("andl %%ecx, %%eax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("andw %%cx, %%ax\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("andb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(dest.value()), "c"(src.value()));
+ } else {
+ ASSERT_NOT_REACHED();
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszpc(new_flags);
+ cpu.taint_flags_from(dest, src);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, dest, src);
+}
+
+template<typename T>
+ALWAYS_INLINE static void op_imul(SoftCPU& cpu, const T& dest, const T& src, T& result_high, T& result_low)
+{
+ bool did_overflow = false;
+ if constexpr (sizeof(T) == 4) {
+ i64 result = (i64)src * (i64)dest;
+ result_low = result & 0xffffffff;
+ result_high = result >> 32;
+ did_overflow = (result > NumericLimits<T>::max() || result < NumericLimits<T>::min());
+ } else if constexpr (sizeof(T) == 2) {
+ i32 result = (i32)src * (i32)dest;
+ result_low = result & 0xffff;
+ result_high = result >> 16;
+ did_overflow = (result > NumericLimits<T>::max() || result < NumericLimits<T>::min());
+ } else if constexpr (sizeof(T) == 1) {
+ i16 result = (i16)src * (i16)dest;
+ result_low = result & 0xff;
+ result_high = result >> 8;
+ did_overflow = (result > NumericLimits<T>::max() || result < NumericLimits<T>::min());
+ }
+
+ if (did_overflow) {
+ cpu.set_cf(true);
+ cpu.set_of(true);
+ } else {
+ cpu.set_cf(false);
+ cpu.set_of(false);
+ }
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_shr(SoftCPU& cpu, T data, ValueWithShadow<u8> steps)
+{
+ if (steps.value() == 0)
+ return shadow_wrap_with_taint_from(data.value(), data, steps);
+
+ u32 result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("shrl %%cl, %%eax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("shrw %%cl, %%ax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("shrb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszapc(new_flags);
+ cpu.taint_flags_from(data, steps);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, data, steps);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_shl(SoftCPU& cpu, T data, ValueWithShadow<u8> steps)
+{
+ if (steps.value() == 0)
+ return shadow_wrap_with_taint_from(data.value(), data, steps);
+
+ u32 result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("shll %%cl, %%eax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("shlw %%cl, %%ax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("shlb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszapc(new_flags);
+ cpu.taint_flags_from(data, steps);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, data, steps);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_shrd(SoftCPU& cpu, T data, T extra_bits, ValueWithShadow<u8> steps)
+{
+ if (steps.value() == 0)
+ return shadow_wrap_with_taint_from(data.value(), data, steps);
+
+ u32 result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("shrd %%cl, %%edx, %%eax\n"
+ : "=a"(result)
+ : "a"(data.value()), "d"(extra_bits.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("shrd %%cl, %%dx, %%ax\n"
+ : "=a"(result)
+ : "a"(data.value()), "d"(extra_bits.value()), "c"(steps.value()));
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszapc(new_flags);
+ cpu.taint_flags_from(data, steps);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, data, steps);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_shld(SoftCPU& cpu, T data, T extra_bits, ValueWithShadow<u8> steps)
+{
+ if (steps.value() == 0)
+ return shadow_wrap_with_taint_from(data.value(), data, steps);
+
+ u32 result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("shld %%cl, %%edx, %%eax\n"
+ : "=a"(result)
+ : "a"(data.value()), "d"(extra_bits.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("shld %%cl, %%dx, %%ax\n"
+ : "=a"(result)
+ : "a"(data.value()), "d"(extra_bits.value()), "c"(steps.value()));
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszapc(new_flags);
+ cpu.taint_flags_from(data, steps);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, data, steps);
+}
+
+template<bool update_dest, bool is_or, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_AL_imm8(Op op, const X86::Instruction& insn)
+{
+ auto dest = al();
+ auto src = shadow_wrap_as_initialized(insn.imm8());
+ auto result = op(*this, dest, src);
+ if (is_or && insn.imm8() == 0xff)
+ result.set_initialized();
+ if (update_dest)
+ set_al(result);
+}
+
+template<bool update_dest, bool is_or, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_AX_imm16(Op op, const X86::Instruction& insn)
+{
+ auto dest = ax();
+ auto src = shadow_wrap_as_initialized(insn.imm16());
+ auto result = op(*this, dest, src);
+ if (is_or && insn.imm16() == 0xffff)
+ result.set_initialized();
+ if (update_dest)
+ set_ax(result);
+}
+
+template<bool update_dest, bool is_or, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_EAX_imm32(Op op, const X86::Instruction& insn)
+{
+ auto dest = eax();
+ auto src = shadow_wrap_as_initialized(insn.imm32());
+ auto result = op(*this, dest, src);
+ if (is_or && insn.imm32() == 0xffffffff)
+ result.set_initialized();
+ if (update_dest)
+ set_eax(result);
+}
+
+template<bool update_dest, bool is_or, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM16_imm16(Op op, const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read16(*this, insn);
+ auto src = shadow_wrap_as_initialized(insn.imm16());
+ auto result = op(*this, dest, src);
+ if (is_or && insn.imm16() == 0xffff)
+ result.set_initialized();
+ if (update_dest)
+ insn.modrm().write16(*this, insn, result);
+}
+
+template<bool update_dest, bool is_or, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM16_imm8(Op op, const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read16(*this, insn);
+ auto src = shadow_wrap_as_initialized<u16>(sign_extended_to<u16>(insn.imm8()));
+ auto result = op(*this, dest, src);
+ if (is_or && src.value() == 0xffff)
+ result.set_initialized();
+ if (update_dest)
+ insn.modrm().write16(*this, insn, result);
+}
+
+template<bool update_dest, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM16_unsigned_imm8(Op op, const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read16(*this, insn);
+ auto src = shadow_wrap_as_initialized(insn.imm8());
+ auto result = op(*this, dest, src);
+ if (update_dest)
+ insn.modrm().write16(*this, insn, result);
+}
+
+template<bool update_dest, bool dont_taint_for_same_operand, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM16_reg16(Op op, const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read16(*this, insn);
+ auto src = const_gpr16(insn.reg16());
+ auto result = op(*this, dest, src);
+ if (dont_taint_for_same_operand && insn.modrm().is_register() && insn.modrm().register_index() == insn.register_index()) {
+ result.set_initialized();
+ m_flags_tainted = false;
+ }
+ if (update_dest)
+ insn.modrm().write16(*this, insn, result);
+}
+
+template<bool update_dest, bool is_or, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM32_imm32(Op op, const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read32(*this, insn);
+ auto src = insn.imm32();
+ auto result = op(*this, dest, shadow_wrap_as_initialized(src));
+ if (is_or && src == 0xffffffff)
+ result.set_initialized();
+ if (update_dest)
+ insn.modrm().write32(*this, insn, result);
+}
+
+template<bool update_dest, bool is_or, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM32_imm8(Op op, const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read32(*this, insn);
+ auto src = sign_extended_to<u32>(insn.imm8());
+ auto result = op(*this, dest, shadow_wrap_as_initialized(src));
+ if (is_or && src == 0xffffffff)
+ result.set_initialized();
+ if (update_dest)
+ insn.modrm().write32(*this, insn, result);
+}
+
+template<bool update_dest, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM32_unsigned_imm8(Op op, const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read32(*this, insn);
+ auto src = shadow_wrap_as_initialized(insn.imm8());
+ auto result = op(*this, dest, src);
+ if (update_dest)
+ insn.modrm().write32(*this, insn, result);
+}
+
+template<bool update_dest, bool dont_taint_for_same_operand, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM32_reg32(Op op, const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read32(*this, insn);
+ auto src = const_gpr32(insn.reg32());
+ auto result = op(*this, dest, src);
+ if (dont_taint_for_same_operand && insn.modrm().is_register() && insn.modrm().register_index() == insn.register_index()) {
+ result.set_initialized();
+ m_flags_tainted = false;
+ }
+ if (update_dest)
+ insn.modrm().write32(*this, insn, result);
+}
+
+template<bool update_dest, bool is_or, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM8_imm8(Op op, const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read8(*this, insn);
+ auto src = insn.imm8();
+ auto result = op(*this, dest, shadow_wrap_as_initialized(src));
+ if (is_or && src == 0xff)
+ result.set_initialized();
+ if (update_dest)
+ insn.modrm().write8(*this, insn, result);
+}
+
+template<bool update_dest, bool dont_taint_for_same_operand, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM8_reg8(Op op, const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read8(*this, insn);
+ auto src = const_gpr8(insn.reg8());
+ auto result = op(*this, dest, src);
+ if (dont_taint_for_same_operand && insn.modrm().is_register() && insn.modrm().register_index() == insn.register_index()) {
+ result.set_initialized();
+ m_flags_tainted = false;
+ }
+ if (update_dest)
+ insn.modrm().write8(*this, insn, result);
+}
+
+template<bool update_dest, bool dont_taint_for_same_operand, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_reg16_RM16(Op op, const X86::Instruction& insn)
+{
+ auto dest = const_gpr16(insn.reg16());
+ auto src = insn.modrm().read16(*this, insn);
+ auto result = op(*this, dest, src);
+ if (dont_taint_for_same_operand && insn.modrm().is_register() && insn.modrm().register_index() == insn.register_index()) {
+ result.set_initialized();
+ m_flags_tainted = false;
+ }
+ if (update_dest)
+ gpr16(insn.reg16()) = result;
+}
+
+template<bool update_dest, bool dont_taint_for_same_operand, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_reg32_RM32(Op op, const X86::Instruction& insn)
+{
+ auto dest = const_gpr32(insn.reg32());
+ auto src = insn.modrm().read32(*this, insn);
+ auto result = op(*this, dest, src);
+ if (dont_taint_for_same_operand && insn.modrm().is_register() && insn.modrm().register_index() == insn.register_index()) {
+ result.set_initialized();
+ m_flags_tainted = false;
+ }
+ if (update_dest)
+ gpr32(insn.reg32()) = result;
+}
+
+template<bool update_dest, bool dont_taint_for_same_operand, typename Op>
+ALWAYS_INLINE void SoftCPU::generic_reg8_RM8(Op op, const X86::Instruction& insn)
+{
+ auto dest = const_gpr8(insn.reg8());
+ auto src = insn.modrm().read8(*this, insn);
+ auto result = op(*this, dest, src);
+ if (dont_taint_for_same_operand && insn.modrm().is_register() && insn.modrm().register_index() == insn.register_index()) {
+ result.set_initialized();
+ m_flags_tainted = false;
+ }
+ if (update_dest)
+ gpr8(insn.reg8()) = result;
+}
+
+template<typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM8_1(Op op, const X86::Instruction& insn)
+{
+ auto data = insn.modrm().read8(*this, insn);
+ insn.modrm().write8(*this, insn, op(*this, data, shadow_wrap_as_initialized<u8>(1)));
+}
+
+template<typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM8_CL(Op op, const X86::Instruction& insn)
+{
+ auto data = insn.modrm().read8(*this, insn);
+ insn.modrm().write8(*this, insn, op(*this, data, cl()));
+}
+
+template<typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM16_1(Op op, const X86::Instruction& insn)
+{
+ auto data = insn.modrm().read16(*this, insn);
+ insn.modrm().write16(*this, insn, op(*this, data, shadow_wrap_as_initialized<u8>(1)));
+}
+
+template<typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM16_CL(Op op, const X86::Instruction& insn)
+{
+ auto data = insn.modrm().read16(*this, insn);
+ insn.modrm().write16(*this, insn, op(*this, data, cl()));
+}
+
+template<typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM32_1(Op op, const X86::Instruction& insn)
+{
+ auto data = insn.modrm().read32(*this, insn);
+ insn.modrm().write32(*this, insn, op(*this, data, shadow_wrap_as_initialized<u8>(1)));
+}
+
+template<typename Op>
+ALWAYS_INLINE void SoftCPU::generic_RM32_CL(Op op, const X86::Instruction& insn)
+{
+ auto data = insn.modrm().read32(*this, insn);
+ insn.modrm().write32(*this, insn, op(*this, data, cl()));
+}
+
+void SoftCPU::AAA(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::AAD(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::AAM(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::AAS(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::ARPL(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::BOUND(const X86::Instruction&) { TODO_INSN(); }
+
+template<typename T>
+ALWAYS_INLINE static T op_bsf(SoftCPU&, T value)
+{
+ return { (typename T::ValueType)__builtin_ctz(value.value()), value.shadow() };
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_bsr(SoftCPU&, T value)
+{
+ typename T::ValueType bit_index = 0;
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("bsrl %%eax, %%edx"
+ : "=d"(bit_index)
+ : "a"(value.value()));
+ }
+ if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("bsrw %%ax, %%dx"
+ : "=d"(bit_index)
+ : "a"(value.value()));
+ }
+ return shadow_wrap_with_taint_from(bit_index, value);
+}
+
+void SoftCPU::BSF_reg16_RM16(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read16(*this, insn);
+ set_zf(!src.value());
+ if (src.value())
+ gpr16(insn.reg16()) = op_bsf(*this, src);
+ taint_flags_from(src);
+}
+
+void SoftCPU::BSF_reg32_RM32(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read32(*this, insn);
+ set_zf(!src.value());
+ if (src.value()) {
+ gpr32(insn.reg32()) = op_bsf(*this, src);
+ taint_flags_from(src);
+ }
+}
+
+void SoftCPU::BSR_reg16_RM16(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read16(*this, insn);
+ set_zf(!src.value());
+ if (src.value()) {
+ gpr16(insn.reg16()) = op_bsr(*this, src);
+ taint_flags_from(src);
+ }
+}
+
+void SoftCPU::BSR_reg32_RM32(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read32(*this, insn);
+ set_zf(!src.value());
+ if (src.value()) {
+ gpr32(insn.reg32()) = op_bsr(*this, src);
+ taint_flags_from(src);
+ }
+}
+
+void SoftCPU::BSWAP_reg32(const X86::Instruction& insn)
+{
+ gpr32(insn.reg32()) = { __builtin_bswap32(gpr32(insn.reg32()).value()), __builtin_bswap32(gpr32(insn.reg32()).shadow()) };
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_bt(T value, T)
+{
+ return value;
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_bts(T value, T bit_mask)
+{
+ return value | bit_mask;
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_btr(T value, T bit_mask)
+{
+ return value & ~bit_mask;
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_btc(T value, T bit_mask)
+{
+ return value ^ bit_mask;
+}
+
+template<bool should_update, typename Op>
+ALWAYS_INLINE void BTx_RM16_reg16(SoftCPU& cpu, const X86::Instruction& insn, Op op)
+{
+ if (insn.modrm().is_register()) {
+ unsigned bit_index = cpu.const_gpr16(insn.reg16()).value() & (X86::TypeTrivia<u16>::bits - 1);
+ auto original = insn.modrm().read16(cpu, insn);
+ u16 bit_mask = 1 << bit_index;
+ u16 result = op(original.value(), bit_mask);
+ cpu.set_cf((original.value() & bit_mask) != 0);
+ cpu.taint_flags_from(cpu.gpr16(insn.reg16()), original);
+ if (should_update)
+ insn.modrm().write16(cpu, insn, shadow_wrap_with_taint_from(result, cpu.gpr16(insn.reg16()), original));
+ return;
+ }
+ // FIXME: Is this supposed to perform a full 16-bit read/modify/write?
+ unsigned bit_offset_in_array = cpu.const_gpr16(insn.reg16()).value() / 8;
+ unsigned bit_offset_in_byte = cpu.const_gpr16(insn.reg16()).value() & 7;
+ auto address = insn.modrm().resolve(cpu, insn);
+ address.set_offset(address.offset() + bit_offset_in_array);
+ auto dest = cpu.read_memory8(address);
+ u8 bit_mask = 1 << bit_offset_in_byte;
+ u8 result = op(dest.value(), bit_mask);
+ cpu.set_cf((dest.value() & bit_mask) != 0);
+ cpu.taint_flags_from(cpu.gpr16(insn.reg16()), dest);
+ if (should_update)
+ cpu.write_memory8(address, shadow_wrap_with_taint_from(result, cpu.gpr16(insn.reg16()), dest));
+}
+
+template<bool should_update, typename Op>
+ALWAYS_INLINE void BTx_RM32_reg32(SoftCPU& cpu, const X86::Instruction& insn, Op op)
+{
+ if (insn.modrm().is_register()) {
+ unsigned bit_index = cpu.const_gpr32(insn.reg32()).value() & (X86::TypeTrivia<u32>::bits - 1);
+ auto original = insn.modrm().read32(cpu, insn);
+ u32 bit_mask = 1 << bit_index;
+ u32 result = op(original.value(), bit_mask);
+ cpu.set_cf((original.value() & bit_mask) != 0);
+ cpu.taint_flags_from(cpu.gpr32(insn.reg32()), original);
+ if (should_update)
+ insn.modrm().write32(cpu, insn, shadow_wrap_with_taint_from(result, cpu.gpr32(insn.reg32()), original));
+ return;
+ }
+ // FIXME: Is this supposed to perform a full 32-bit read/modify/write?
+ unsigned bit_offset_in_array = cpu.const_gpr32(insn.reg32()).value() / 8;
+ unsigned bit_offset_in_byte = cpu.const_gpr32(insn.reg32()).value() & 7;
+ auto address = insn.modrm().resolve(cpu, insn);
+ address.set_offset(address.offset() + bit_offset_in_array);
+ auto dest = cpu.read_memory8(address);
+ u8 bit_mask = 1 << bit_offset_in_byte;
+ u8 result = op(dest.value(), bit_mask);
+ cpu.set_cf((dest.value() & bit_mask) != 0);
+ cpu.taint_flags_from(cpu.gpr32(insn.reg32()), dest);
+ if (should_update)
+ cpu.write_memory8(address, shadow_wrap_with_taint_from(result, cpu.gpr32(insn.reg32()), dest));
+}
+
+template<bool should_update, typename Op>
+ALWAYS_INLINE void BTx_RM16_imm8(SoftCPU& cpu, const X86::Instruction& insn, Op op)
+{
+ unsigned bit_index = insn.imm8() & (X86::TypeTrivia<u16>::mask);
+
+ // FIXME: Support higher bit indices
+ ASSERT(bit_index < 16);
+
+ auto original = insn.modrm().read16(cpu, insn);
+ u16 bit_mask = 1 << bit_index;
+ auto result = op(original.value(), bit_mask);
+ cpu.set_cf((original.value() & bit_mask) != 0);
+ cpu.taint_flags_from(original);
+ if (should_update)
+ insn.modrm().write16(cpu, insn, shadow_wrap_with_taint_from(result, original));
+}
+
+template<bool should_update, typename Op>
+ALWAYS_INLINE void BTx_RM32_imm8(SoftCPU& cpu, const X86::Instruction& insn, Op op)
+{
+ unsigned bit_index = insn.imm8() & (X86::TypeTrivia<u32>::mask);
+
+ // FIXME: Support higher bit indices
+ ASSERT(bit_index < 32);
+
+ auto original = insn.modrm().read32(cpu, insn);
+ u32 bit_mask = 1 << bit_index;
+ auto result = op(original.value(), bit_mask);
+ cpu.set_cf((original.value() & bit_mask) != 0);
+ cpu.taint_flags_from(original);
+ if (should_update)
+ insn.modrm().write32(cpu, insn, shadow_wrap_with_taint_from(result, original));
+}
+
+#define DEFINE_GENERIC_BTx_INSN_HANDLERS(mnemonic, op, update_dest) \
+ void SoftCPU::mnemonic##_RM32_reg32(const X86::Instruction& insn) { BTx_RM32_reg32<update_dest>(*this, insn, op<u32>); } \
+ void SoftCPU::mnemonic##_RM16_reg16(const X86::Instruction& insn) { BTx_RM16_reg16<update_dest>(*this, insn, op<u16>); } \
+ void SoftCPU::mnemonic##_RM32_imm8(const X86::Instruction& insn) { BTx_RM32_imm8<update_dest>(*this, insn, op<u32>); } \
+ void SoftCPU::mnemonic##_RM16_imm8(const X86::Instruction& insn) { BTx_RM16_imm8<update_dest>(*this, insn, op<u16>); }
+
+DEFINE_GENERIC_BTx_INSN_HANDLERS(BTS, op_bts, true);
+DEFINE_GENERIC_BTx_INSN_HANDLERS(BTR, op_btr, true);
+DEFINE_GENERIC_BTx_INSN_HANDLERS(BTC, op_btc, true);
+DEFINE_GENERIC_BTx_INSN_HANDLERS(BT, op_bt, false);
+
+void SoftCPU::CALL_FAR_mem16(const X86::Instruction&)
+{
+ TODO();
+}
+void SoftCPU::CALL_FAR_mem32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::CALL_RM16(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::CALL_RM32(const X86::Instruction& insn)
+{
+ push32(shadow_wrap_as_initialized(eip()));
+ auto address = insn.modrm().read32(*this, insn);
+ warn_if_uninitialized(address, "call rm32");
+ set_eip(address.value());
+}
+
+void SoftCPU::CALL_imm16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::CALL_imm16_imm16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::CALL_imm16_imm32(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::CALL_imm32(const X86::Instruction& insn)
+{
+ push32(shadow_wrap_as_initialized(eip()));
+ set_eip(eip() + (i32)insn.imm32());
+}
+
+void SoftCPU::CBW(const X86::Instruction&)
+{
+ set_ah(shadow_wrap_with_taint_from<u8>((al().value() & 0x80) ? 0xff : 0x00, al()));
+}
+
+void SoftCPU::CDQ(const X86::Instruction&)
+{
+ if (eax().value() & 0x80000000)
+ set_edx(shadow_wrap_with_taint_from<u32>(0xffffffff, eax()));
+ else
+ set_edx(shadow_wrap_with_taint_from<u32>(0, eax()));
+}
+
+void SoftCPU::CLC(const X86::Instruction&)
+{
+ set_cf(false);
+}
+
+void SoftCPU::CLD(const X86::Instruction&)
+{
+ set_df(false);
+}
+
+void SoftCPU::CLI(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::CLTS(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::CMC(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::CMOVcc_reg16_RM16(const X86::Instruction& insn)
+{
+ warn_if_flags_tainted("cmovcc reg16, rm16");
+ if (evaluate_condition(insn.cc()))
+ gpr16(insn.reg16()) = insn.modrm().read16(*this, insn);
+}
+
+void SoftCPU::CMOVcc_reg32_RM32(const X86::Instruction& insn)
+{
+ warn_if_flags_tainted("cmovcc reg32, rm32");
+ if (evaluate_condition(insn.cc()))
+ gpr32(insn.reg32()) = insn.modrm().read32(*this, insn);
+}
+
+template<typename T>
+ALWAYS_INLINE static void do_cmps(SoftCPU& cpu, const X86::Instruction& insn)
+{
+ auto src_segment = cpu.segment(insn.segment_prefix().value_or(X86::SegmentRegister::DS));
+ cpu.do_once_or_repeat<true>(insn, [&] {
+ auto src = cpu.read_memory<T>({ src_segment, cpu.source_index(insn.a32()).value() });
+ auto dest = cpu.read_memory<T>({ cpu.es(), cpu.destination_index(insn.a32()).value() });
+ op_sub(cpu, dest, src);
+ cpu.step_source_index(insn.a32(), sizeof(T));
+ cpu.step_destination_index(insn.a32(), sizeof(T));
+ });
+}
+
+void SoftCPU::CMPSB(const X86::Instruction& insn)
+{
+ do_cmps<u8>(*this, insn);
+}
+
+void SoftCPU::CMPSD(const X86::Instruction& insn)
+{
+ do_cmps<u32>(*this, insn);
+}
+
+void SoftCPU::CMPSW(const X86::Instruction& insn)
+{
+ do_cmps<u16>(*this, insn);
+}
+
+void SoftCPU::CMPXCHG_RM16_reg16(const X86::Instruction& insn)
+{
+ auto current = insn.modrm().read16(*this, insn);
+ taint_flags_from(current, ax());
+ if (current.value() == ax().value()) {
+ set_zf(true);
+ insn.modrm().write16(*this, insn, const_gpr16(insn.reg16()));
+ } else {
+ set_zf(false);
+ set_ax(current);
+ }
+}
+
+void SoftCPU::CMPXCHG_RM32_reg32(const X86::Instruction& insn)
+{
+ auto current = insn.modrm().read32(*this, insn);
+ taint_flags_from(current, eax());
+ if (current.value() == eax().value()) {
+ set_zf(true);
+ insn.modrm().write32(*this, insn, const_gpr32(insn.reg32()));
+ } else {
+ set_zf(false);
+ set_eax(current);
+ }
+}
+
+void SoftCPU::CMPXCHG_RM8_reg8(const X86::Instruction& insn)
+{
+ auto current = insn.modrm().read8(*this, insn);
+ taint_flags_from(current, al());
+ if (current.value() == al().value()) {
+ set_zf(true);
+ insn.modrm().write8(*this, insn, const_gpr8(insn.reg8()));
+ } else {
+ set_zf(false);
+ set_al(current);
+ }
+}
+
+void SoftCPU::CPUID(const X86::Instruction&)
+{
+ if (eax().value() == 0) {
+ set_eax(shadow_wrap_as_initialized<u32>(1));
+ set_ebx(shadow_wrap_as_initialized<u32>(0x6c6c6548));
+ set_edx(shadow_wrap_as_initialized<u32>(0x6972466f));
+ set_ecx(shadow_wrap_as_initialized<u32>(0x73646e65));
+ return;
+ }
+
+ if (eax().value() == 1) {
+ u32 stepping = 0;
+ u32 model = 1;
+ u32 family = 3;
+ u32 type = 0;
+ set_eax(shadow_wrap_as_initialized<u32>(stepping | (model << 4) | (family << 8) | (type << 12)));
+ set_ebx(shadow_wrap_as_initialized<u32>(0));
+ set_edx(shadow_wrap_as_initialized<u32>((1 << 15))); // Features (CMOV)
+ set_ecx(shadow_wrap_as_initialized<u32>(0));
+ return;
+ }
+
+ dbgln("Unhandled CPUID with eax={:08x}", eax().value());
+}
+
+void SoftCPU::CWD(const X86::Instruction&)
+{
+ set_dx(shadow_wrap_with_taint_from<u16>((ax().value() & 0x8000) ? 0xffff : 0x0000, ax()));
+}
+
+void SoftCPU::CWDE(const X86::Instruction&)
+{
+ set_eax(shadow_wrap_with_taint_from(sign_extended_to<u32>(ax().value()), ax()));
+}
+
+void SoftCPU::DAA(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::DAS(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::DEC_RM16(const X86::Instruction& insn)
+{
+ insn.modrm().write16(*this, insn, op_dec(*this, insn.modrm().read16(*this, insn)));
+}
+
+void SoftCPU::DEC_RM32(const X86::Instruction& insn)
+{
+ insn.modrm().write32(*this, insn, op_dec(*this, insn.modrm().read32(*this, insn)));
+}
+
+void SoftCPU::DEC_RM8(const X86::Instruction& insn)
+{
+ insn.modrm().write8(*this, insn, op_dec(*this, insn.modrm().read8(*this, insn)));
+}
+
+void SoftCPU::DEC_reg16(const X86::Instruction& insn)
+{
+ gpr16(insn.reg16()) = op_dec(*this, const_gpr16(insn.reg16()));
+}
+
+void SoftCPU::DEC_reg32(const X86::Instruction& insn)
+{
+ gpr32(insn.reg32()) = op_dec(*this, const_gpr32(insn.reg32()));
+}
+
+void SoftCPU::DIV_RM16(const X86::Instruction& insn)
+{
+ auto divisor = insn.modrm().read16(*this, insn);
+ if (divisor.value() == 0) {
+ reportln("Divide by zero");
+ TODO();
+ }
+ u32 dividend = ((u32)dx().value() << 16) | ax().value();
+ auto quotient = dividend / divisor.value();
+ if (quotient > NumericLimits<u16>::max()) {
+ reportln("Divide overflow");
+ TODO();
+ }
+
+ auto remainder = dividend % divisor.value();
+ auto original_ax = ax();
+
+ set_ax(shadow_wrap_with_taint_from<u16>(quotient, original_ax, dx()));
+ set_dx(shadow_wrap_with_taint_from<u16>(remainder, original_ax, dx()));
+}
+
+void SoftCPU::DIV_RM32(const X86::Instruction& insn)
+{
+ auto divisor = insn.modrm().read32(*this, insn);
+ if (divisor.value() == 0) {
+ reportln("Divide by zero");
+ TODO();
+ }
+ u64 dividend = ((u64)edx().value() << 32) | eax().value();
+ auto quotient = dividend / divisor.value();
+ if (quotient > NumericLimits<u32>::max()) {
+ reportln("Divide overflow");
+ TODO();
+ }
+
+ auto remainder = dividend % divisor.value();
+ auto original_eax = eax();
+
+ set_eax(shadow_wrap_with_taint_from<u32>(quotient, original_eax, edx(), divisor));
+ set_edx(shadow_wrap_with_taint_from<u32>(remainder, original_eax, edx(), divisor));
+}
+
+void SoftCPU::DIV_RM8(const X86::Instruction& insn)
+{
+ auto divisor = insn.modrm().read8(*this, insn);
+ if (divisor.value() == 0) {
+ reportln("Divide by zero");
+ TODO();
+ }
+ u16 dividend = ax().value();
+ auto quotient = dividend / divisor.value();
+ if (quotient > NumericLimits<u8>::max()) {
+ reportln("Divide overflow");
+ TODO();
+ }
+
+ auto remainder = dividend % divisor.value();
+ auto original_ax = ax();
+ set_al(shadow_wrap_with_taint_from<u8>(quotient, original_ax, divisor));
+ set_ah(shadow_wrap_with_taint_from<u8>(remainder, original_ax, divisor));
+}
+
+void SoftCPU::ENTER16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::ENTER32(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::ESCAPE(const X86::Instruction&)
+{
+ reportln("FIXME: x87 floating-point support");
+ m_emulator.dump_backtrace();
+ TODO();
+}
+
+void SoftCPU::FADD_RM32(const X86::Instruction& insn)
+{
+ // XXX look at ::INC_foo for how mem/reg stuff is handled, and use that here too to make sure this is only called for mem32 ops
+ if (insn.modrm().is_register()) {
+ fpu_set(0, fpu_get(insn.modrm().register_index()) + fpu_get(0));
+ } else {
+ auto new_f32 = insn.modrm().read32(*this, insn);
+ // FIXME: Respect shadow values
+ auto f32 = bit_cast<float>(new_f32.value());
+ fpu_set(0, fpu_get(0) + f32);
+ }
+}
+
+void SoftCPU::FMUL_RM32(const X86::Instruction& insn)
+{
+ // XXX look at ::INC_foo for how mem/reg stuff is handled, and use that here too to make sure this is only called for mem32 ops
+ if (insn.modrm().is_register()) {
+ fpu_set(0, fpu_get(0) * fpu_get(insn.modrm().register_index()));
+ } else {
+ auto new_f32 = insn.modrm().read32(*this, insn);
+ // FIXME: Respect shadow values
+ auto f32 = bit_cast<float>(new_f32.value());
+ fpu_set(0, fpu_get(0) * f32);
+ }
+}
+
+void SoftCPU::FCOM_RM32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FCOMP_RM32(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FSUB_RM32(const X86::Instruction& insn)
+{
+ if (insn.modrm().is_register()) {
+ fpu_set(0, fpu_get(0) - fpu_get(insn.modrm().register_index()));
+ } else {
+ auto new_f32 = insn.modrm().read32(*this, insn);
+ // FIXME: Respect shadow values
+ auto f32 = bit_cast<float>(new_f32.value());
+ fpu_set(0, fpu_get(0) - f32);
+ }
+}
+
+void SoftCPU::FSUBR_RM32(const X86::Instruction& insn)
+{
+ if (insn.modrm().is_register()) {
+ fpu_set(0, fpu_get(insn.modrm().register_index()) - fpu_get(0));
+ } else {
+ auto new_f32 = insn.modrm().read32(*this, insn);
+ // FIXME: Respect shadow values
+ auto f32 = bit_cast<float>(new_f32.value());
+ fpu_set(0, f32 - fpu_get(0));
+ }
+}
+
+void SoftCPU::FDIV_RM32(const X86::Instruction& insn)
+{
+ if (insn.modrm().is_register()) {
+ fpu_set(0, fpu_get(0) / fpu_get(insn.modrm().register_index()));
+ } else {
+ auto new_f32 = insn.modrm().read32(*this, insn);
+ // FIXME: Respect shadow values
+ auto f32 = bit_cast<float>(new_f32.value());
+ // FIXME: Raise IA on + infinity / +-infinitiy, +-0 / +-0, raise Z on finite / +-0
+ fpu_set(0, fpu_get(0) / f32);
+ }
+}
+
+void SoftCPU::FDIVR_RM32(const X86::Instruction& insn)
+{
+ if (insn.modrm().is_register()) {
+ fpu_set(0, fpu_get(insn.modrm().register_index()) / fpu_get(0));
+ } else {
+ auto new_f32 = insn.modrm().read32(*this, insn);
+ // FIXME: Respect shadow values
+ auto f32 = bit_cast<float>(new_f32.value());
+ // FIXME: Raise IA on + infinity / +-infinitiy, +-0 / +-0, raise Z on finite / +-0
+ fpu_set(0, f32 / fpu_get(0));
+ }
+}
+
+void SoftCPU::FLD_RM32(const X86::Instruction& insn)
+{
+ if (insn.modrm().is_register()) {
+ fpu_push(fpu_get(insn.modrm().register_index()));
+ } else {
+ auto new_f32 = insn.modrm().read32(*this, insn);
+ // FIXME: Respect shadow values
+ fpu_push(bit_cast<float>(new_f32.value()));
+ }
+}
+
+void SoftCPU::FXCH(const X86::Instruction& insn)
+{
+ ASSERT(insn.modrm().is_register());
+ auto tmp = fpu_get(0);
+ fpu_set(0, fpu_get(insn.modrm().register_index()));
+ fpu_set(insn.modrm().register_index(), tmp);
+}
+
+void SoftCPU::FST_RM32(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ float f32 = (float)fpu_get(0);
+ // FIXME: Respect shadow values
+ insn.modrm().write32(*this, insn, shadow_wrap_as_initialized(bit_cast<u32>(f32)));
+}
+
+void SoftCPU::FNOP(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FSTP_RM32(const X86::Instruction& insn)
+{
+ FST_RM32(insn);
+ fpu_pop();
+}
+
+void SoftCPU::FLDENV(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FCHS(const X86::Instruction&)
+{
+ fpu_set(0, -fpu_get(0));
+}
+
+void SoftCPU::FABS(const X86::Instruction&)
+{
+ fpu_set(0, __builtin_fabs(fpu_get(0)));
+}
+
+void SoftCPU::FTST(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FXAM(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FLDCW(const X86::Instruction& insn)
+{
+ m_fpu_cw = insn.modrm().read16(*this, insn);
+}
+
+void SoftCPU::FLD1(const X86::Instruction&)
+{
+ fpu_push(1.0);
+}
+
+void SoftCPU::FLDL2T(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FLDL2E(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FLDPI(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FLDLG2(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FLDLN2(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FLDZ(const X86::Instruction&)
+{
+ fpu_push(0.0);
+}
+
+void SoftCPU::FNSTENV(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::F2XM1(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FYL2X(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FPTAN(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FPATAN(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FXTRACT(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FPREM1(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FDECSTP(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FINCSTP(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FNSTCW(const X86::Instruction& insn)
+{
+ insn.modrm().write16(*this, insn, m_fpu_cw);
+}
+
+void SoftCPU::FPREM(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FYL2XP1(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FSQRT(const X86::Instruction&)
+{
+ fpu_set(0, sqrt(fpu_get(0)));
+}
+
+void SoftCPU::FSINCOS(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FRNDINT(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FSCALE(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FSIN(const X86::Instruction&)
+{
+ fpu_set(0, sin(fpu_get(0)));
+}
+
+void SoftCPU::FCOS(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FIADD_RM32(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m32int = (i32)insn.modrm().read32(*this, insn).value();
+ // FIXME: Respect shadow values
+ fpu_set(0, fpu_get(0) + (long double)m32int);
+}
+
+void SoftCPU::FCMOVB(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FIMUL_RM32(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m32int = (i32)insn.modrm().read32(*this, insn).value();
+ // FIXME: Respect shadow values
+ fpu_set(0, fpu_get(0) * (long double)m32int);
+}
+
+void SoftCPU::FCMOVE(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FICOM_RM32(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FCMOVBE(const X86::Instruction& insn)
+{
+ if (evaluate_condition(6))
+ fpu_set(0, fpu_get(insn.rm() & 7));
+}
+
+void SoftCPU::FICOMP_RM32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FCMOVU(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FISUB_RM32(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m32int = (i32)insn.modrm().read32(*this, insn).value();
+ // FIXME: Respect shadow values
+ fpu_set(0, fpu_get(0) - (long double)m32int);
+}
+
+void SoftCPU::FISUBR_RM32(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m32int = (i32)insn.modrm().read32(*this, insn).value();
+ // FIXME: Respect shadow values
+ fpu_set(0, (long double)m32int - fpu_get(0));
+}
+
+void SoftCPU::FUCOMPP(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FIDIV_RM32(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m32int = (i32)insn.modrm().read32(*this, insn).value();
+ // FIXME: Respect shadow values
+ // FIXME: Raise IA on 0 / _=0, raise Z on finite / +-0
+ fpu_set(0, fpu_get(0) / (long double)m32int);
+}
+
+void SoftCPU::FIDIVR_RM32(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m32int = (i32)insn.modrm().read32(*this, insn).value();
+ // FIXME: Respect shadow values
+ // FIXME: Raise IA on 0 / _=0, raise Z on finite / +-0
+ fpu_set(0, (long double)m32int / fpu_get(0));
+}
+
+void SoftCPU::FILD_RM32(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m32int = (i32)insn.modrm().read32(*this, insn).value();
+ // FIXME: Respect shadow values
+ fpu_push((long double)m32int);
+}
+
+void SoftCPU::FCMOVNB(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FISTTP_RM32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FCMOVNE(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FIST_RM32(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto f = fpu_get(0);
+ // FIXME: Respect rounding mode in m_fpu_cw.
+ auto i32 = static_cast<int32_t>(f);
+ // FIXME: Respect shadow values
+ insn.modrm().write32(*this, insn, shadow_wrap_as_initialized(bit_cast<u32>(i32)));
+}
+
+void SoftCPU::FCMOVNBE(const X86::Instruction& insn)
+{
+ if (evaluate_condition(7))
+ fpu_set(0, fpu_get(insn.rm() & 7));
+}
+
+void SoftCPU::FISTP_RM32(const X86::Instruction& insn)
+{
+ FIST_RM32(insn);
+ fpu_pop();
+}
+
+void SoftCPU::FCMOVNU(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FNENI(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FNDISI(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FNCLEX(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FNINIT(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FNSETPM(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FLD_RM80(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FUCOMI(const X86::Instruction& insn)
+{
+ auto i = insn.rm() & 7;
+ // FIXME: Unordered comparison checks.
+ // FIXME: QNaN / exception handling.
+ // FIXME: Set C0, C2, C3 in FPU status word.
+ if (__builtin_isnan(fpu_get(0)) || __builtin_isnan(fpu_get(i))) {
+ set_zf(true);
+ set_pf(true);
+ set_cf(true);
+ } else {
+ set_zf(fpu_get(0) == fpu_get(i));
+ set_pf(false);
+ set_cf(fpu_get(0) < fpu_get(i));
+ set_of(false);
+ }
+
+ // FIXME: Taint should be based on ST(0) and ST(i)
+ m_flags_tainted = false;
+}
+
+void SoftCPU::FCOMI(const X86::Instruction& insn)
+{
+ auto i = insn.rm() & 7;
+ // FIXME: QNaN / exception handling.
+ // FIXME: Set C0, C2, C3 in FPU status word.
+ set_zf(fpu_get(0) == fpu_get(i));
+ set_pf(false);
+ set_cf(fpu_get(0) < fpu_get(i));
+ set_of(false);
+
+ // FIXME: Taint should be based on ST(0) and ST(i)
+ m_flags_tainted = false;
+}
+
+void SoftCPU::FSTP_RM80(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FADD_RM64(const X86::Instruction& insn)
+{
+ // XXX look at ::INC_foo for how mem/reg stuff is handled, and use that here too to make sure this is only called for mem64 ops
+ if (insn.modrm().is_register()) {
+ fpu_set(insn.modrm().register_index(), fpu_get(insn.modrm().register_index()) + fpu_get(0));
+ } else {
+ auto new_f64 = insn.modrm().read64(*this, insn);
+ // FIXME: Respect shadow values
+ auto f64 = bit_cast<double>(new_f64.value());
+ fpu_set(0, fpu_get(0) + f64);
+ }
+}
+
+void SoftCPU::FMUL_RM64(const X86::Instruction& insn)
+{
+ // XXX look at ::INC_foo for how mem/reg stuff is handled, and use that here too to make sure this is only called for mem64 ops
+ if (insn.modrm().is_register()) {
+ fpu_set(insn.modrm().register_index(), fpu_get(insn.modrm().register_index()) * fpu_get(0));
+ } else {
+ auto new_f64 = insn.modrm().read64(*this, insn);
+ // FIXME: Respect shadow values
+ auto f64 = bit_cast<double>(new_f64.value());
+ fpu_set(0, fpu_get(0) * f64);
+ }
+}
+
+void SoftCPU::FCOM_RM64(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FCOMP_RM64(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FSUB_RM64(const X86::Instruction& insn)
+{
+ if (insn.modrm().is_register()) {
+ fpu_set(insn.modrm().register_index(), fpu_get(insn.modrm().register_index()) - fpu_get(0));
+ } else {
+ auto new_f64 = insn.modrm().read64(*this, insn);
+ // FIXME: Respect shadow values
+ auto f64 = bit_cast<double>(new_f64.value());
+ fpu_set(0, fpu_get(0) - f64);
+ }
+}
+
+void SoftCPU::FSUBR_RM64(const X86::Instruction& insn)
+{
+ if (insn.modrm().is_register()) {
+ fpu_set(insn.modrm().register_index(), fpu_get(insn.modrm().register_index()) - fpu_get(0));
+ } else {
+ auto new_f64 = insn.modrm().read64(*this, insn);
+ // FIXME: Respect shadow values
+ auto f64 = bit_cast<double>(new_f64.value());
+ fpu_set(0, f64 - fpu_get(0));
+ }
+}
+
+void SoftCPU::FDIV_RM64(const X86::Instruction& insn)
+{
+ if (insn.modrm().is_register()) {
+ fpu_set(insn.modrm().register_index(), fpu_get(insn.modrm().register_index()) / fpu_get(0));
+ } else {
+ auto new_f64 = insn.modrm().read64(*this, insn);
+ // FIXME: Respect shadow values
+ auto f64 = bit_cast<double>(new_f64.value());
+ // FIXME: Raise IA on + infinity / +-infinitiy, +-0 / +-0, raise Z on finite / +-0
+ fpu_set(0, fpu_get(0) / f64);
+ }
+}
+
+void SoftCPU::FDIVR_RM64(const X86::Instruction& insn)
+{
+ if (insn.modrm().is_register()) {
+ // XXX this is FDIVR, Instruction decodes this weirdly
+ //fpu_set(insn.modrm().register_index(), fpu_get(0) / fpu_get(insn.modrm().register_index()));
+ fpu_set(insn.modrm().register_index(), fpu_get(insn.modrm().register_index()) / fpu_get(0));
+ } else {
+ auto new_f64 = insn.modrm().read64(*this, insn);
+ // FIXME: Respect shadow values
+ auto f64 = bit_cast<double>(new_f64.value());
+ // FIXME: Raise IA on + infinity / +-infinitiy, +-0 / +-0, raise Z on finite / +-0
+ fpu_set(0, f64 / fpu_get(0));
+ }
+}
+
+void SoftCPU::FLD_RM64(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto new_f64 = insn.modrm().read64(*this, insn);
+ // FIXME: Respect shadow values
+ fpu_push(bit_cast<double>(new_f64.value()));
+}
+
+void SoftCPU::FFREE(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FISTTP_RM64(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FST_RM64(const X86::Instruction& insn)
+{
+ if (insn.modrm().is_register()) {
+ fpu_set(insn.modrm().register_index(), fpu_get(0));
+ } else {
+ // FIXME: Respect shadow values
+ double f64 = (double)fpu_get(0);
+ insn.modrm().write64(*this, insn, shadow_wrap_as_initialized(bit_cast<u64>(f64)));
+ }
+}
+
+void SoftCPU::FSTP_RM64(const X86::Instruction& insn)
+{
+ FST_RM64(insn);
+ fpu_pop();
+}
+
+void SoftCPU::FRSTOR(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FUCOM(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FUCOMP(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FNSAVE(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FNSTSW(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FIADD_RM16(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m16int = (i16)insn.modrm().read16(*this, insn).value();
+ // FIXME: Respect shadow values
+ fpu_set(0, fpu_get(0) + (long double)m16int);
+}
+
+void SoftCPU::FADDP(const X86::Instruction& insn)
+{
+ ASSERT(insn.modrm().is_register());
+ fpu_set(insn.modrm().register_index(), fpu_get(insn.modrm().register_index()) + fpu_get(0));
+ fpu_pop();
+}
+
+void SoftCPU::FIMUL_RM16(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m16int = (i16)insn.modrm().read16(*this, insn).value();
+ // FIXME: Respect shadow values
+ fpu_set(0, fpu_get(0) * (long double)m16int);
+}
+
+void SoftCPU::FMULP(const X86::Instruction& insn)
+{
+ ASSERT(insn.modrm().is_register());
+ fpu_set(insn.modrm().register_index(), fpu_get(insn.modrm().register_index()) * fpu_get(0));
+ fpu_pop();
+}
+
+void SoftCPU::FICOM_RM16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FICOMP_RM16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FCOMPP(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FISUB_RM16(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m16int = (i16)insn.modrm().read16(*this, insn).value();
+ // FIXME: Respect shadow values
+ fpu_set(0, fpu_get(0) - (long double)m16int);
+}
+
+void SoftCPU::FSUBRP(const X86::Instruction& insn)
+{
+ ASSERT(insn.modrm().is_register());
+ fpu_set(insn.modrm().register_index(), fpu_get(0) - fpu_get(insn.modrm().register_index()));
+ fpu_pop();
+}
+
+void SoftCPU::FISUBR_RM16(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m16int = (i16)insn.modrm().read16(*this, insn).value();
+ // FIXME: Respect shadow values
+ fpu_set(0, (long double)m16int - fpu_get(0));
+}
+
+void SoftCPU::FSUBP(const X86::Instruction& insn)
+{
+ ASSERT(insn.modrm().is_register());
+ fpu_set(insn.modrm().register_index(), fpu_get(insn.modrm().register_index()) - fpu_get(0));
+ fpu_pop();
+}
+
+void SoftCPU::FIDIV_RM16(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m16int = (i16)insn.modrm().read16(*this, insn).value();
+ // FIXME: Respect shadow values
+ // FIXME: Raise IA on 0 / _=0, raise Z on finite / +-0
+ fpu_set(0, fpu_get(0) / (long double)m16int);
+}
+
+void SoftCPU::FDIVRP(const X86::Instruction& insn)
+{
+ ASSERT(insn.modrm().is_register());
+ // FIXME: Raise IA on + infinity / +-infinitiy, +-0 / +-0, raise Z on finite / +-0
+ fpu_set(insn.modrm().register_index(), fpu_get(0) / fpu_get(insn.modrm().register_index()));
+ fpu_pop();
+}
+
+void SoftCPU::FIDIVR_RM16(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m16int = (i16)insn.modrm().read16(*this, insn).value();
+ // FIXME: Respect shadow values
+ // FIXME: Raise IA on 0 / _=0, raise Z on finite / +-0
+ fpu_set(0, (long double)m16int / fpu_get(0));
+}
+
+void SoftCPU::FDIVP(const X86::Instruction& insn)
+{
+ ASSERT(insn.modrm().is_register());
+ // FIXME: Raise IA on + infinity / +-infinitiy, +-0 / +-0, raise Z on finite / +-0
+ fpu_set(insn.modrm().register_index(), fpu_get(insn.modrm().register_index()) / fpu_get(0));
+ fpu_pop();
+}
+
+void SoftCPU::FILD_RM16(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m16int = (i16)insn.modrm().read16(*this, insn).value();
+ // FIXME: Respect shadow values
+ fpu_push((long double)m16int);
+}
+
+void SoftCPU::FFREEP(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FISTTP_RM16(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FIST_RM16(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto f = fpu_get(0);
+ // FIXME: Respect rounding mode in m_fpu_cw.
+ auto i16 = static_cast<int16_t>(f);
+ // FIXME: Respect shadow values
+ insn.modrm().write16(*this, insn, shadow_wrap_as_initialized(bit_cast<u16>(i16)));
+}
+
+void SoftCPU::FISTP_RM16(const X86::Instruction& insn)
+{
+ FIST_RM16(insn);
+ fpu_pop();
+}
+
+void SoftCPU::FBLD_M80(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::FNSTSW_AX(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FILD_RM64(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto m64int = (i64)insn.modrm().read64(*this, insn).value();
+ // FIXME: Respect shadow values
+ fpu_push((long double)m64int);
+}
+
+void SoftCPU::FUCOMIP(const X86::Instruction& insn)
+{
+ FUCOMI(insn);
+ fpu_pop();
+}
+
+void SoftCPU::FBSTP_M80(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::FCOMIP(const X86::Instruction& insn)
+{
+ FCOMI(insn);
+ fpu_pop();
+}
+
+void SoftCPU::FISTP_RM64(const X86::Instruction& insn)
+{
+ ASSERT(!insn.modrm().is_register());
+ auto f = fpu_pop();
+ // FIXME: Respect rounding mode in m_fpu_cw.
+ auto i64 = static_cast<int64_t>(f);
+ // FIXME: Respect shadow values
+ insn.modrm().write64(*this, insn, shadow_wrap_as_initialized(bit_cast<u64>(i64)));
+}
+void SoftCPU::HLT(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::IDIV_RM16(const X86::Instruction& insn)
+{
+ auto divisor_with_shadow = insn.modrm().read16(*this, insn);
+ auto divisor = (i16)divisor_with_shadow.value();
+ if (divisor == 0) {
+ reportln("Divide by zero");
+ TODO();
+ }
+ i32 dividend = (i32)(((u32)dx().value() << 16) | (u32)ax().value());
+ i32 result = dividend / divisor;
+ if (result > NumericLimits<i16>::max() || result < NumericLimits<i16>::min()) {
+ reportln("Divide overflow");
+ TODO();
+ }
+
+ auto original_ax = ax();
+ set_ax(shadow_wrap_with_taint_from<u16>(result, original_ax, dx(), divisor_with_shadow));
+ set_dx(shadow_wrap_with_taint_from<u16>(dividend % divisor, original_ax, dx(), divisor_with_shadow));
+}
+
+void SoftCPU::IDIV_RM32(const X86::Instruction& insn)
+{
+ auto divisor_with_shadow = insn.modrm().read32(*this, insn);
+ auto divisor = (i32)divisor_with_shadow.value();
+ if (divisor == 0) {
+ reportln("Divide by zero");
+ TODO();
+ }
+ i64 dividend = (i64)(((u64)edx().value() << 32) | (u64)eax().value());
+ i64 result = dividend / divisor;
+ if (result > NumericLimits<i32>::max() || result < NumericLimits<i32>::min()) {
+ reportln("Divide overflow");
+ TODO();
+ }
+
+ auto original_eax = eax();
+ set_eax(shadow_wrap_with_taint_from<u32>(result, original_eax, edx(), divisor_with_shadow));
+ set_edx(shadow_wrap_with_taint_from<u32>(dividend % divisor, original_eax, edx(), divisor_with_shadow));
+}
+
+void SoftCPU::IDIV_RM8(const X86::Instruction& insn)
+{
+ auto divisor_with_shadow = insn.modrm().read8(*this, insn);
+ auto divisor = (i8)divisor_with_shadow.value();
+ if (divisor == 0) {
+ reportln("Divide by zero");
+ TODO();
+ }
+ i16 dividend = ax().value();
+ i16 result = dividend / divisor;
+ if (result > NumericLimits<i8>::max() || result < NumericLimits<i8>::min()) {
+ reportln("Divide overflow");
+ TODO();
+ }
+
+ auto original_ax = ax();
+ set_al(shadow_wrap_with_taint_from<u8>(result, divisor_with_shadow, original_ax));
+ set_ah(shadow_wrap_with_taint_from<u8>(dividend % divisor, divisor_with_shadow, original_ax));
+}
+
+void SoftCPU::IMUL_RM16(const X86::Instruction& insn)
+{
+ i16 result_high;
+ i16 result_low;
+ auto src = insn.modrm().read16(*this, insn);
+ op_imul<i16>(*this, src.value(), ax().value(), result_high, result_low);
+ gpr16(X86::RegisterDX) = shadow_wrap_with_taint_from<u16>(result_high, src, ax());
+ gpr16(X86::RegisterAX) = shadow_wrap_with_taint_from<u16>(result_low, src, ax());
+}
+
+void SoftCPU::IMUL_RM32(const X86::Instruction& insn)
+{
+ i32 result_high;
+ i32 result_low;
+ auto src = insn.modrm().read32(*this, insn);
+ op_imul<i32>(*this, src.value(), eax().value(), result_high, result_low);
+ gpr32(X86::RegisterEDX) = shadow_wrap_with_taint_from<u32>(result_high, src, eax());
+ gpr32(X86::RegisterEAX) = shadow_wrap_with_taint_from<u32>(result_low, src, eax());
+}
+
+void SoftCPU::IMUL_RM8(const X86::Instruction& insn)
+{
+ i8 result_high;
+ i8 result_low;
+ auto src = insn.modrm().read8(*this, insn);
+ op_imul<i8>(*this, src.value(), al().value(), result_high, result_low);
+ gpr8(X86::RegisterAH) = shadow_wrap_with_taint_from<u8>(result_high, src, al());
+ gpr8(X86::RegisterAL) = shadow_wrap_with_taint_from<u8>(result_low, src, al());
+}
+
+void SoftCPU::IMUL_reg16_RM16(const X86::Instruction& insn)
+{
+ i16 result_high;
+ i16 result_low;
+ auto src = insn.modrm().read16(*this, insn);
+ op_imul<i16>(*this, gpr16(insn.reg16()).value(), src.value(), result_high, result_low);
+ gpr16(insn.reg16()) = shadow_wrap_with_taint_from<u16>(result_low, src, gpr16(insn.reg16()));
+}
+
+void SoftCPU::IMUL_reg16_RM16_imm16(const X86::Instruction& insn)
+{
+ i16 result_high;
+ i16 result_low;
+ auto src = insn.modrm().read16(*this, insn);
+ op_imul<i16>(*this, src.value(), insn.imm16(), result_high, result_low);
+ gpr16(insn.reg16()) = shadow_wrap_with_taint_from<u16>(result_low, src);
+}
+
+void SoftCPU::IMUL_reg16_RM16_imm8(const X86::Instruction& insn)
+{
+ i16 result_high;
+ i16 result_low;
+ auto src = insn.modrm().read16(*this, insn);
+ op_imul<i16>(*this, src.value(), sign_extended_to<i16>(insn.imm8()), result_high, result_low);
+ gpr16(insn.reg16()) = shadow_wrap_with_taint_from<u16>(result_low, src);
+}
+
+void SoftCPU::IMUL_reg32_RM32(const X86::Instruction& insn)
+{
+ i32 result_high;
+ i32 result_low;
+ auto src = insn.modrm().read32(*this, insn);
+ op_imul<i32>(*this, gpr32(insn.reg32()).value(), src.value(), result_high, result_low);
+ gpr32(insn.reg32()) = shadow_wrap_with_taint_from<u32>(result_low, src, gpr32(insn.reg32()));
+}
+
+void SoftCPU::IMUL_reg32_RM32_imm32(const X86::Instruction& insn)
+{
+ i32 result_high;
+ i32 result_low;
+ auto src = insn.modrm().read32(*this, insn);
+ op_imul<i32>(*this, src.value(), insn.imm32(), result_high, result_low);
+ gpr32(insn.reg32()) = shadow_wrap_with_taint_from<u32>(result_low, src);
+}
+
+void SoftCPU::IMUL_reg32_RM32_imm8(const X86::Instruction& insn)
+{
+ i32 result_high;
+ i32 result_low;
+ auto src = insn.modrm().read32(*this, insn);
+ op_imul<i32>(*this, src.value(), sign_extended_to<i32>(insn.imm8()), result_high, result_low);
+ gpr32(insn.reg32()) = shadow_wrap_with_taint_from<u32>(result_low, src);
+}
+
+void SoftCPU::INC_RM16(const X86::Instruction& insn)
+{
+ insn.modrm().write16(*this, insn, op_inc(*this, insn.modrm().read16(*this, insn)));
+}
+
+void SoftCPU::INC_RM32(const X86::Instruction& insn)
+{
+ insn.modrm().write32(*this, insn, op_inc(*this, insn.modrm().read32(*this, insn)));
+}
+
+void SoftCPU::INC_RM8(const X86::Instruction& insn)
+{
+ insn.modrm().write8(*this, insn, op_inc(*this, insn.modrm().read8(*this, insn)));
+}
+
+void SoftCPU::INC_reg16(const X86::Instruction& insn)
+{
+ gpr16(insn.reg16()) = op_inc(*this, const_gpr16(insn.reg16()));
+}
+
+void SoftCPU::INC_reg32(const X86::Instruction& insn)
+{
+ gpr32(insn.reg32()) = op_inc(*this, const_gpr32(insn.reg32()));
+}
+
+void SoftCPU::INSB(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::INSD(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::INSW(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::INT3(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::INTO(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::INT_imm8(const X86::Instruction& insn)
+{
+ ASSERT(insn.imm8() == 0x82);
+ // FIXME: virt_syscall should take ValueWithShadow and whine about uninitialized arguments
+ set_eax(shadow_wrap_as_initialized(m_emulator.virt_syscall(eax().value(), edx().value(), ecx().value(), ebx().value())));
+}
+
+void SoftCPU::INVLPG(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::IN_AL_DX(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::IN_AL_imm8(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::IN_AX_DX(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::IN_AX_imm8(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::IN_EAX_DX(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::IN_EAX_imm8(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::IRET(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::JCXZ_imm8(const X86::Instruction& insn)
+{
+ if (insn.a32()) {
+ warn_if_uninitialized(ecx(), "jecxz imm8");
+ if (ecx().value() == 0)
+ set_eip(eip() + (i8)insn.imm8());
+ } else {
+ warn_if_uninitialized(cx(), "jcxz imm8");
+ if (cx().value() == 0)
+ set_eip(eip() + (i8)insn.imm8());
+ }
+}
+
+void SoftCPU::JMP_FAR_mem16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::JMP_FAR_mem32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::JMP_RM16(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::JMP_RM32(const X86::Instruction& insn)
+{
+ set_eip(insn.modrm().read32(*this, insn).value());
+}
+
+void SoftCPU::JMP_imm16(const X86::Instruction& insn)
+{
+ set_eip(eip() + (i16)insn.imm16());
+}
+
+void SoftCPU::JMP_imm16_imm16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::JMP_imm16_imm32(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::JMP_imm32(const X86::Instruction& insn)
+{
+ set_eip(eip() + (i32)insn.imm32());
+}
+
+void SoftCPU::JMP_short_imm8(const X86::Instruction& insn)
+{
+ set_eip(eip() + (i8)insn.imm8());
+}
+
+void SoftCPU::Jcc_NEAR_imm(const X86::Instruction& insn)
+{
+ warn_if_flags_tainted("jcc near imm32");
+ if (evaluate_condition(insn.cc()))
+ set_eip(eip() + (i32)insn.imm32());
+}
+
+void SoftCPU::Jcc_imm8(const X86::Instruction& insn)
+{
+ warn_if_flags_tainted("jcc imm8");
+ if (evaluate_condition(insn.cc()))
+ set_eip(eip() + (i8)insn.imm8());
+}
+
+void SoftCPU::LAHF(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LAR_reg16_RM16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LAR_reg32_RM32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LDS_reg16_mem16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LDS_reg32_mem32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LEAVE16(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::LEAVE32(const X86::Instruction&)
+{
+ auto new_ebp = read_memory32({ ss(), ebp().value() });
+ set_esp({ ebp().value() + 4, ebp().shadow() });
+ set_ebp(new_ebp);
+}
+
+void SoftCPU::LEA_reg16_mem16(const X86::Instruction& insn)
+{
+ // FIXME: Respect shadow values
+ gpr16(insn.reg16()) = shadow_wrap_as_initialized<u16>(insn.modrm().resolve(*this, insn).offset());
+}
+
+void SoftCPU::LEA_reg32_mem32(const X86::Instruction& insn)
+{
+ // FIXME: Respect shadow values
+ gpr32(insn.reg32()) = shadow_wrap_as_initialized<u32>(insn.modrm().resolve(*this, insn).offset());
+}
+
+void SoftCPU::LES_reg16_mem16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LES_reg32_mem32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LFS_reg16_mem16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LFS_reg32_mem32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LGDT(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LGS_reg16_mem16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LGS_reg32_mem32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LIDT(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LLDT_RM16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LMSW_RM16(const X86::Instruction&) { TODO_INSN(); }
+
+template<typename T>
+ALWAYS_INLINE static void do_lods(SoftCPU& cpu, const X86::Instruction& insn)
+{
+ auto src_segment = cpu.segment(insn.segment_prefix().value_or(X86::SegmentRegister::DS));
+ cpu.do_once_or_repeat<true>(insn, [&] {
+ auto src = cpu.read_memory<T>({ src_segment, cpu.source_index(insn.a32()).value() });
+ cpu.gpr<T>(X86::RegisterAL) = src;
+ cpu.step_source_index(insn.a32(), sizeof(T));
+ });
+}
+
+void SoftCPU::LODSB(const X86::Instruction& insn)
+{
+ do_lods<u8>(*this, insn);
+}
+
+void SoftCPU::LODSD(const X86::Instruction& insn)
+{
+ do_lods<u32>(*this, insn);
+}
+
+void SoftCPU::LODSW(const X86::Instruction& insn)
+{
+ do_lods<u16>(*this, insn);
+}
+
+void SoftCPU::LOOPNZ_imm8(const X86::Instruction& insn)
+{
+ warn_if_flags_tainted("loopnz");
+ if (insn.a32()) {
+ set_ecx({ ecx().value() - 1, ecx().shadow() });
+ if (ecx().value() != 0 && !zf())
+ set_eip(eip() + (i8)insn.imm8());
+ } else {
+ set_cx({ (u16)(cx().value() - 1), cx().shadow() });
+ if (cx().value() != 0 && !zf())
+ set_eip(eip() + (i8)insn.imm8());
+ }
+}
+void SoftCPU::LOOPZ_imm8(const X86::Instruction& insn)
+{
+ warn_if_flags_tainted("loopz");
+ if (insn.a32()) {
+ set_ecx({ ecx().value() - 1, ecx().shadow() });
+ if (ecx().value() != 0 && zf())
+ set_eip(eip() + (i8)insn.imm8());
+ } else {
+ set_cx({ (u16)(cx().value() - 1), cx().shadow() });
+ if (cx().value() != 0 && zf())
+ set_eip(eip() + (i8)insn.imm8());
+ }
+}
+
+void SoftCPU::LOOP_imm8(const X86::Instruction& insn)
+{
+ if (insn.a32()) {
+ set_ecx({ ecx().value() - 1, ecx().shadow() });
+ if (ecx().value() != 0)
+ set_eip(eip() + (i8)insn.imm8());
+ } else {
+ set_cx({ (u16)(cx().value() - 1), cx().shadow() });
+ if (cx().value() != 0)
+ set_eip(eip() + (i8)insn.imm8());
+ }
+}
+
+void SoftCPU::LSL_reg16_RM16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LSL_reg32_RM32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LSS_reg16_mem16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LSS_reg32_mem32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::LTR_RM16(const X86::Instruction&) { TODO_INSN(); }
+
+template<typename T>
+ALWAYS_INLINE static void do_movs(SoftCPU& cpu, const X86::Instruction& insn)
+{
+ auto src_segment = cpu.segment(insn.segment_prefix().value_or(X86::SegmentRegister::DS));
+ cpu.do_once_or_repeat<false>(insn, [&] {
+ auto src = cpu.read_memory<T>({ src_segment, cpu.source_index(insn.a32()).value() });
+ cpu.write_memory<T>({ cpu.es(), cpu.destination_index(insn.a32()).value() }, src);
+ cpu.step_source_index(insn.a32(), sizeof(T));
+ cpu.step_destination_index(insn.a32(), sizeof(T));
+ });
+}
+
+void SoftCPU::MOVSB(const X86::Instruction& insn)
+{
+ do_movs<u8>(*this, insn);
+}
+
+void SoftCPU::MOVSD(const X86::Instruction& insn)
+{
+ do_movs<u32>(*this, insn);
+}
+
+void SoftCPU::MOVSW(const X86::Instruction& insn)
+{
+ do_movs<u16>(*this, insn);
+}
+
+void SoftCPU::MOVSX_reg16_RM8(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read8(*this, insn);
+ gpr16(insn.reg16()) = shadow_wrap_with_taint_from<u16>(sign_extended_to<u16>(src.value()), src);
+}
+
+void SoftCPU::MOVSX_reg32_RM16(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read16(*this, insn);
+ gpr32(insn.reg32()) = shadow_wrap_with_taint_from<u32>(sign_extended_to<u32>(src.value()), src);
+}
+
+void SoftCPU::MOVSX_reg32_RM8(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read8(*this, insn);
+ gpr32(insn.reg32()) = shadow_wrap_with_taint_from<u32>(sign_extended_to<u32>(src.value()), src);
+}
+
+void SoftCPU::MOVZX_reg16_RM8(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read8(*this, insn);
+ gpr16(insn.reg16()) = ValueWithShadow<u16>(src.value(), 0x0100 | (src.shadow() & 0xff));
+}
+
+void SoftCPU::MOVZX_reg32_RM16(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read16(*this, insn);
+ gpr32(insn.reg32()) = ValueWithShadow<u32>(src.value(), 0x01010000 | (src.shadow() & 0xffff));
+}
+
+void SoftCPU::MOVZX_reg32_RM8(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read8(*this, insn);
+ gpr32(insn.reg32()) = ValueWithShadow<u32>(src.value(), 0x01010100 | (src.shadow() & 0xff));
+}
+
+void SoftCPU::MOV_AL_moff8(const X86::Instruction& insn)
+{
+ set_al(read_memory8({ segment(insn.segment_prefix().value_or(X86::SegmentRegister::DS)), insn.imm_address() }));
+}
+
+void SoftCPU::MOV_AX_moff16(const X86::Instruction& insn)
+{
+ set_ax(read_memory16({ segment(insn.segment_prefix().value_or(X86::SegmentRegister::DS)), insn.imm_address() }));
+}
+
+void SoftCPU::MOV_CR_reg32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::MOV_DR_reg32(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::MOV_EAX_moff32(const X86::Instruction& insn)
+{
+ set_eax(read_memory32({ segment(insn.segment_prefix().value_or(X86::SegmentRegister::DS)), insn.imm_address() }));
+}
+
+void SoftCPU::MOV_RM16_imm16(const X86::Instruction& insn)
+{
+ insn.modrm().write16(*this, insn, shadow_wrap_as_initialized(insn.imm16()));
+}
+
+void SoftCPU::MOV_RM16_reg16(const X86::Instruction& insn)
+{
+ insn.modrm().write16(*this, insn, const_gpr16(insn.reg16()));
+}
+
+void SoftCPU::MOV_RM16_seg(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::MOV_RM32_imm32(const X86::Instruction& insn)
+{
+ insn.modrm().write32(*this, insn, shadow_wrap_as_initialized(insn.imm32()));
+}
+
+void SoftCPU::MOV_RM32_reg32(const X86::Instruction& insn)
+{
+ insn.modrm().write32(*this, insn, const_gpr32(insn.reg32()));
+}
+
+void SoftCPU::MOV_RM8_imm8(const X86::Instruction& insn)
+{
+ insn.modrm().write8(*this, insn, shadow_wrap_as_initialized(insn.imm8()));
+}
+
+void SoftCPU::MOV_RM8_reg8(const X86::Instruction& insn)
+{
+ insn.modrm().write8(*this, insn, const_gpr8(insn.reg8()));
+}
+
+void SoftCPU::MOV_moff16_AX(const X86::Instruction& insn)
+{
+ write_memory16({ segment(insn.segment_prefix().value_or(X86::SegmentRegister::DS)), insn.imm_address() }, ax());
+}
+
+void SoftCPU::MOV_moff32_EAX(const X86::Instruction& insn)
+{
+ write_memory32({ segment(insn.segment_prefix().value_or(X86::SegmentRegister::DS)), insn.imm_address() }, eax());
+}
+
+void SoftCPU::MOV_moff8_AL(const X86::Instruction& insn)
+{
+ write_memory8({ segment(insn.segment_prefix().value_or(X86::SegmentRegister::DS)), insn.imm_address() }, al());
+}
+
+void SoftCPU::MOV_reg16_RM16(const X86::Instruction& insn)
+{
+ gpr16(insn.reg16()) = insn.modrm().read16(*this, insn);
+}
+
+void SoftCPU::MOV_reg16_imm16(const X86::Instruction& insn)
+{
+ gpr16(insn.reg16()) = shadow_wrap_as_initialized(insn.imm16());
+}
+
+void SoftCPU::MOV_reg32_CR(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::MOV_reg32_DR(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::MOV_reg32_RM32(const X86::Instruction& insn)
+{
+ gpr32(insn.reg32()) = insn.modrm().read32(*this, insn);
+}
+
+void SoftCPU::MOV_reg32_imm32(const X86::Instruction& insn)
+{
+ gpr32(insn.reg32()) = shadow_wrap_as_initialized(insn.imm32());
+}
+
+void SoftCPU::MOV_reg8_RM8(const X86::Instruction& insn)
+{
+ gpr8(insn.reg8()) = insn.modrm().read8(*this, insn);
+}
+
+void SoftCPU::MOV_reg8_imm8(const X86::Instruction& insn)
+{
+ gpr8(insn.reg8()) = shadow_wrap_as_initialized(insn.imm8());
+}
+
+void SoftCPU::MOV_seg_RM16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::MOV_seg_RM32(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::MUL_RM16(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read16(*this, insn);
+ u32 result = (u32)ax().value() * (u32)src.value();
+ auto original_ax = ax();
+ set_ax(shadow_wrap_with_taint_from<u16>(result & 0xffff, src, original_ax));
+ set_dx(shadow_wrap_with_taint_from<u16>(result >> 16, src, original_ax));
+ taint_flags_from(src, original_ax);
+
+ set_cf(dx().value() != 0);
+ set_of(dx().value() != 0);
+}
+
+void SoftCPU::MUL_RM32(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read32(*this, insn);
+ u64 result = (u64)eax().value() * (u64)src.value();
+ auto original_eax = eax();
+ set_eax(shadow_wrap_with_taint_from<u32>(result, src, original_eax));
+ set_edx(shadow_wrap_with_taint_from<u32>(result >> 32, src, original_eax));
+ taint_flags_from(src, original_eax);
+
+ set_cf(edx().value() != 0);
+ set_of(edx().value() != 0);
+}
+
+void SoftCPU::MUL_RM8(const X86::Instruction& insn)
+{
+ auto src = insn.modrm().read8(*this, insn);
+ u16 result = (u16)al().value() * src.value();
+ auto original_al = al();
+ set_ax(shadow_wrap_with_taint_from(result, src, original_al));
+ taint_flags_from(src, original_al);
+
+ set_cf((result & 0xff00) != 0);
+ set_of((result & 0xff00) != 0);
+}
+
+void SoftCPU::NEG_RM16(const X86::Instruction& insn)
+{
+ insn.modrm().write16(*this, insn, op_sub<ValueWithShadow<u16>>(*this, shadow_wrap_as_initialized<u16>(0), insn.modrm().read16(*this, insn)));
+}
+
+void SoftCPU::NEG_RM32(const X86::Instruction& insn)
+{
+ insn.modrm().write32(*this, insn, op_sub<ValueWithShadow<u32>>(*this, shadow_wrap_as_initialized<u32>(0), insn.modrm().read32(*this, insn)));
+}
+
+void SoftCPU::NEG_RM8(const X86::Instruction& insn)
+{
+ insn.modrm().write8(*this, insn, op_sub<ValueWithShadow<u8>>(*this, shadow_wrap_as_initialized<u8>(0), insn.modrm().read8(*this, insn)));
+}
+
+void SoftCPU::NOP(const X86::Instruction&)
+{
+}
+
+void SoftCPU::NOT_RM16(const X86::Instruction& insn)
+{
+ auto data = insn.modrm().read16(*this, insn);
+ insn.modrm().write16(*this, insn, ValueWithShadow<u16>(~data.value(), data.shadow()));
+}
+
+void SoftCPU::NOT_RM32(const X86::Instruction& insn)
+{
+ auto data = insn.modrm().read32(*this, insn);
+ insn.modrm().write32(*this, insn, ValueWithShadow<u32>(~data.value(), data.shadow()));
+}
+
+void SoftCPU::NOT_RM8(const X86::Instruction& insn)
+{
+ auto data = insn.modrm().read8(*this, insn);
+ insn.modrm().write8(*this, insn, ValueWithShadow<u8>(~data.value(), data.shadow()));
+}
+
+void SoftCPU::OUTSB(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::OUTSD(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::OUTSW(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::OUT_DX_AL(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::OUT_DX_AX(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::OUT_DX_EAX(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::OUT_imm8_AL(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::OUT_imm8_AX(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::OUT_imm8_EAX(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::PADDB_mm1_mm2m64(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::PADDW_mm1_mm2m64(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::PADDD_mm1_mm2m64(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::POPA(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::POPAD(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::POPF(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::POPFD(const X86::Instruction&)
+{
+ auto popped_value = pop32();
+ m_eflags &= ~0x00fcffff;
+ m_eflags |= popped_value.value() & 0x00fcffff;
+ taint_flags_from(popped_value);
+}
+
+void SoftCPU::POP_DS(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::POP_ES(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::POP_FS(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::POP_GS(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::POP_RM16(const X86::Instruction& insn)
+{
+ insn.modrm().write16(*this, insn, pop16());
+}
+
+void SoftCPU::POP_RM32(const X86::Instruction& insn)
+{
+ insn.modrm().write32(*this, insn, pop32());
+}
+
+void SoftCPU::POP_SS(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::POP_reg16(const X86::Instruction& insn)
+{
+ gpr16(insn.reg16()) = pop16();
+}
+
+void SoftCPU::POP_reg32(const X86::Instruction& insn)
+{
+ gpr32(insn.reg32()) = pop32();
+}
+
+void SoftCPU::PUSHA(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::PUSHAD(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::PUSHF(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::PUSHFD(const X86::Instruction&)
+{
+ // FIXME: Respect shadow flags when they exist!
+ push32(shadow_wrap_as_initialized(m_eflags & 0x00fcffff));
+}
+
+void SoftCPU::PUSH_CS(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::PUSH_DS(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::PUSH_ES(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::PUSH_FS(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::PUSH_GS(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::PUSH_RM16(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::PUSH_RM32(const X86::Instruction& insn)
+{
+ push32(insn.modrm().read32(*this, insn));
+}
+
+void SoftCPU::PUSH_SP_8086_80186(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::PUSH_SS(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::PUSH_imm16(const X86::Instruction& insn)
+{
+ push16(shadow_wrap_as_initialized(insn.imm16()));
+}
+
+void SoftCPU::PUSH_imm32(const X86::Instruction& insn)
+{
+ push32(shadow_wrap_as_initialized(insn.imm32()));
+}
+
+void SoftCPU::PUSH_imm8(const X86::Instruction& insn)
+{
+ ASSERT(!insn.has_operand_size_override_prefix());
+ push32(shadow_wrap_as_initialized<u32>(sign_extended_to<i32>(insn.imm8())));
+}
+
+void SoftCPU::PUSH_reg16(const X86::Instruction& insn)
+{
+ push16(gpr16(insn.reg16()));
+}
+
+void SoftCPU::PUSH_reg32(const X86::Instruction& insn)
+{
+ push32(gpr32(insn.reg32()));
+
+ if (m_secret_handshake_state == 2) {
+ m_secret_data[0] = gpr32(insn.reg32()).value();
+ ++m_secret_handshake_state;
+ } else if (m_secret_handshake_state == 3) {
+ m_secret_data[1] = gpr32(insn.reg32()).value();
+ ++m_secret_handshake_state;
+ } else if (m_secret_handshake_state == 4) {
+ m_secret_data[2] = gpr32(insn.reg32()).value();
+ m_secret_handshake_state = 0;
+ did_receive_secret_data();
+ }
+}
+
+template<typename T, bool cf>
+ALWAYS_INLINE static T op_rcl_impl(SoftCPU& cpu, T data, ValueWithShadow<u8> steps)
+{
+ if (steps.value() == 0)
+ return shadow_wrap_with_taint_from(data.value(), data, steps);
+
+ u32 result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (cf)
+ asm volatile("stc");
+ else
+ asm volatile("clc");
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("rcll %%cl, %%eax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("rclw %%cl, %%ax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("rclb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oc(new_flags);
+ cpu.taint_flags_from(data, steps);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, data, steps);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_rcl(SoftCPU& cpu, T data, ValueWithShadow<u8> steps)
+{
+ cpu.warn_if_flags_tainted("rcl");
+ if (cpu.cf())
+ return op_rcl_impl<T, true>(cpu, data, steps);
+ return op_rcl_impl<T, false>(cpu, data, steps);
+}
+
+DEFINE_GENERIC_SHIFT_ROTATE_INSN_HANDLERS(RCL, op_rcl)
+
+template<typename T, bool cf>
+ALWAYS_INLINE static T op_rcr_impl(SoftCPU& cpu, T data, ValueWithShadow<u8> steps)
+{
+ if (steps.value() == 0)
+ return shadow_wrap_with_taint_from(data.value(), data, steps);
+
+ u32 result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (cf)
+ asm volatile("stc");
+ else
+ asm volatile("clc");
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("rcrl %%cl, %%eax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("rcrw %%cl, %%ax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("rcrb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oc(new_flags);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, data, steps);
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_rcr(SoftCPU& cpu, T data, ValueWithShadow<u8> steps)
+{
+ cpu.warn_if_flags_tainted("rcr");
+ if (cpu.cf())
+ return op_rcr_impl<T, true>(cpu, data, steps);
+ return op_rcr_impl<T, false>(cpu, data, steps);
+}
+
+DEFINE_GENERIC_SHIFT_ROTATE_INSN_HANDLERS(RCR, op_rcr)
+
+void SoftCPU::RDTSC(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::RET(const X86::Instruction& insn)
+{
+ ASSERT(!insn.has_operand_size_override_prefix());
+ auto ret_address = pop32();
+ warn_if_uninitialized(ret_address, "ret");
+ set_eip(ret_address.value());
+}
+
+void SoftCPU::RETF(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::RETF_imm16(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::RET_imm16(const X86::Instruction& insn)
+{
+ ASSERT(!insn.has_operand_size_override_prefix());
+ auto ret_address = pop32();
+ warn_if_uninitialized(ret_address, "ret imm16");
+ set_eip(ret_address.value());
+ set_esp({ esp().value() + insn.imm16(), esp().shadow() });
+}
+
+template<typename T>
+ALWAYS_INLINE static T op_rol(SoftCPU& cpu, T data, ValueWithShadow<u8> steps)
+{
+ if (steps.value() == 0)
+ return shadow_wrap_with_taint_from(data.value(), data, steps);
+
+ u32 result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("roll %%cl, %%eax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("rolw %%cl, %%ax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("rolb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oc(new_flags);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, data, steps);
+}
+
+DEFINE_GENERIC_SHIFT_ROTATE_INSN_HANDLERS(ROL, op_rol)
+
+template<typename T>
+ALWAYS_INLINE static T op_ror(SoftCPU& cpu, T data, ValueWithShadow<u8> steps)
+{
+ if (steps.value() == 0)
+ return shadow_wrap_with_taint_from(data.value(), data, steps);
+
+ u32 result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("rorl %%cl, %%eax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("rorw %%cl, %%ax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("rorb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oc(new_flags);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, data, steps);
+}
+
+DEFINE_GENERIC_SHIFT_ROTATE_INSN_HANDLERS(ROR, op_ror)
+
+void SoftCPU::SAHF(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::SALC(const X86::Instruction&)
+{
+ // FIXME: Respect shadow flags once they exists!
+ set_al(shadow_wrap_as_initialized<u8>(cf() ? 0xff : 0x00));
+
+ if (m_secret_handshake_state < 2)
+ ++m_secret_handshake_state;
+ else
+ m_secret_handshake_state = 0;
+}
+
+template<typename T>
+static T op_sar(SoftCPU& cpu, T data, ValueWithShadow<u8> steps)
+{
+ if (steps.value() == 0)
+ return shadow_wrap_with_taint_from(data.value(), data, steps);
+
+ u32 result = 0;
+ u32 new_flags = 0;
+
+ if constexpr (sizeof(typename T::ValueType) == 4) {
+ asm volatile("sarl %%cl, %%eax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 2) {
+ asm volatile("sarw %%cl, %%ax\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ } else if constexpr (sizeof(typename T::ValueType) == 1) {
+ asm volatile("sarb %%cl, %%al\n"
+ : "=a"(result)
+ : "a"(data.value()), "c"(steps.value()));
+ }
+
+ asm volatile(
+ "pushf\n"
+ "pop %%ebx"
+ : "=b"(new_flags));
+
+ cpu.set_flags_oszapc(new_flags);
+ return shadow_wrap_with_taint_from<typename T::ValueType>(result, data, steps);
+}
+
+DEFINE_GENERIC_SHIFT_ROTATE_INSN_HANDLERS(SAR, op_sar)
+
+template<typename T>
+ALWAYS_INLINE static void do_scas(SoftCPU& cpu, const X86::Instruction& insn)
+{
+ cpu.do_once_or_repeat<true>(insn, [&] {
+ auto src = cpu.const_gpr<T>(X86::RegisterAL);
+ auto dest = cpu.read_memory<T>({ cpu.es(), cpu.destination_index(insn.a32()).value() });
+ op_sub(cpu, dest, src);
+ cpu.step_destination_index(insn.a32(), sizeof(T));
+ });
+}
+
+void SoftCPU::SCASB(const X86::Instruction& insn)
+{
+ do_scas<u8>(*this, insn);
+}
+
+void SoftCPU::SCASD(const X86::Instruction& insn)
+{
+ do_scas<u32>(*this, insn);
+}
+
+void SoftCPU::SCASW(const X86::Instruction& insn)
+{
+ do_scas<u16>(*this, insn);
+}
+
+void SoftCPU::SETcc_RM8(const X86::Instruction& insn)
+{
+ warn_if_flags_tainted("setcc");
+ insn.modrm().write8(*this, insn, shadow_wrap_as_initialized<u8>(evaluate_condition(insn.cc())));
+}
+
+void SoftCPU::SGDT(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::SHLD_RM16_reg16_CL(const X86::Instruction& insn)
+{
+ insn.modrm().write16(*this, insn, op_shld(*this, insn.modrm().read16(*this, insn), const_gpr16(insn.reg16()), cl()));
+}
+
+void SoftCPU::SHLD_RM16_reg16_imm8(const X86::Instruction& insn)
+{
+ insn.modrm().write16(*this, insn, op_shld(*this, insn.modrm().read16(*this, insn), const_gpr16(insn.reg16()), shadow_wrap_as_initialized(insn.imm8())));
+}
+
+void SoftCPU::SHLD_RM32_reg32_CL(const X86::Instruction& insn)
+{
+ insn.modrm().write32(*this, insn, op_shld(*this, insn.modrm().read32(*this, insn), const_gpr32(insn.reg32()), cl()));
+}
+
+void SoftCPU::SHLD_RM32_reg32_imm8(const X86::Instruction& insn)
+{
+ insn.modrm().write32(*this, insn, op_shld(*this, insn.modrm().read32(*this, insn), const_gpr32(insn.reg32()), shadow_wrap_as_initialized(insn.imm8())));
+}
+
+DEFINE_GENERIC_SHIFT_ROTATE_INSN_HANDLERS(SHL, op_shl)
+
+void SoftCPU::SHRD_RM16_reg16_CL(const X86::Instruction& insn)
+{
+ insn.modrm().write16(*this, insn, op_shrd(*this, insn.modrm().read16(*this, insn), const_gpr16(insn.reg16()), cl()));
+}
+
+void SoftCPU::SHRD_RM16_reg16_imm8(const X86::Instruction& insn)
+{
+ insn.modrm().write16(*this, insn, op_shrd(*this, insn.modrm().read16(*this, insn), const_gpr16(insn.reg16()), shadow_wrap_as_initialized(insn.imm8())));
+}
+
+void SoftCPU::SHRD_RM32_reg32_CL(const X86::Instruction& insn)
+{
+ insn.modrm().write32(*this, insn, op_shrd(*this, insn.modrm().read32(*this, insn), const_gpr32(insn.reg32()), cl()));
+}
+
+void SoftCPU::SHRD_RM32_reg32_imm8(const X86::Instruction& insn)
+{
+ insn.modrm().write32(*this, insn, op_shrd(*this, insn.modrm().read32(*this, insn), const_gpr32(insn.reg32()), shadow_wrap_as_initialized(insn.imm8())));
+}
+
+DEFINE_GENERIC_SHIFT_ROTATE_INSN_HANDLERS(SHR, op_shr)
+
+void SoftCPU::SIDT(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::SLDT_RM16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::SMSW_RM16(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::STC(const X86::Instruction&)
+{
+ set_cf(true);
+}
+
+void SoftCPU::STD(const X86::Instruction&)
+{
+ set_df(true);
+}
+
+void SoftCPU::STI(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::STOSB(const X86::Instruction& insn)
+{
+ if (insn.has_rep_prefix() && !df()) {
+ // Fast path for 8-bit forward memory fill.
+ if (m_emulator.mmu().fast_fill_memory8({ es(), destination_index(insn.a32()).value() }, ecx().value(), al())) {
+ if (insn.a32()) {
+ // FIXME: Should an uninitialized ECX taint EDI here?
+ set_edi({ (u32)(edi().value() + ecx().value()), edi().shadow() });
+ set_ecx(shadow_wrap_as_initialized<u32>(0));
+ } else {
+ // FIXME: Should an uninitialized CX taint DI here?
+ set_di({ (u16)(di().value() + cx().value()), di().shadow() });
+ set_cx(shadow_wrap_as_initialized<u16>(0));
+ }
+ return;
+ }
+ }
+
+ do_once_or_repeat<false>(insn, [&] {
+ write_memory8({ es(), destination_index(insn.a32()).value() }, al());
+ step_destination_index(insn.a32(), 1);
+ });
+}
+
+void SoftCPU::STOSD(const X86::Instruction& insn)
+{
+ if (insn.has_rep_prefix() && !df()) {
+ // Fast path for 32-bit forward memory fill.
+ if (m_emulator.mmu().fast_fill_memory32({ es(), destination_index(insn.a32()).value() }, ecx().value(), eax())) {
+ if (insn.a32()) {
+ // FIXME: Should an uninitialized ECX taint EDI here?
+ set_edi({ (u32)(edi().value() + (ecx().value() * sizeof(u32))), edi().shadow() });
+ set_ecx(shadow_wrap_as_initialized<u32>(0));
+ } else {
+ // FIXME: Should an uninitialized CX taint DI here?
+ set_di({ (u16)(di().value() + (cx().value() * sizeof(u32))), di().shadow() });
+ set_cx(shadow_wrap_as_initialized<u16>(0));
+ }
+ return;
+ }
+ }
+
+ do_once_or_repeat<false>(insn, [&] {
+ write_memory32({ es(), destination_index(insn.a32()).value() }, eax());
+ step_destination_index(insn.a32(), 4);
+ });
+}
+
+void SoftCPU::STOSW(const X86::Instruction& insn)
+{
+ do_once_or_repeat<false>(insn, [&] {
+ write_memory16({ es(), destination_index(insn.a32()).value() }, ax());
+ step_destination_index(insn.a32(), 2);
+ });
+}
+
+void SoftCPU::STR_RM16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::UD0(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::UD1(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::UD2(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::VERR_RM16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::VERW_RM16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::WAIT(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::WBINVD(const X86::Instruction&) { TODO_INSN(); }
+
+void SoftCPU::XADD_RM16_reg16(const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read16(*this, insn);
+ auto src = const_gpr16(insn.reg16());
+ auto result = op_add(*this, dest, src);
+ gpr16(insn.reg16()) = dest;
+ insn.modrm().write16(*this, insn, result);
+}
+
+void SoftCPU::XADD_RM32_reg32(const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read32(*this, insn);
+ auto src = const_gpr32(insn.reg32());
+ auto result = op_add(*this, dest, src);
+ gpr32(insn.reg32()) = dest;
+ insn.modrm().write32(*this, insn, result);
+}
+
+void SoftCPU::XADD_RM8_reg8(const X86::Instruction& insn)
+{
+ auto dest = insn.modrm().read8(*this, insn);
+ auto src = const_gpr8(insn.reg8());
+ auto result = op_add(*this, dest, src);
+ gpr8(insn.reg8()) = dest;
+ insn.modrm().write8(*this, insn, result);
+}
+
+void SoftCPU::XCHG_AX_reg16(const X86::Instruction& insn)
+{
+ auto temp = gpr16(insn.reg16());
+ gpr16(insn.reg16()) = ax();
+ set_ax(temp);
+}
+
+void SoftCPU::XCHG_EAX_reg32(const X86::Instruction& insn)
+{
+ auto temp = gpr32(insn.reg32());
+ gpr32(insn.reg32()) = eax();
+ set_eax(temp);
+}
+
+void SoftCPU::XCHG_reg16_RM16(const X86::Instruction& insn)
+{
+ auto temp = insn.modrm().read16(*this, insn);
+ insn.modrm().write16(*this, insn, const_gpr16(insn.reg16()));
+ gpr16(insn.reg16()) = temp;
+}
+
+void SoftCPU::XCHG_reg32_RM32(const X86::Instruction& insn)
+{
+ auto temp = insn.modrm().read32(*this, insn);
+ insn.modrm().write32(*this, insn, const_gpr32(insn.reg32()));
+ gpr32(insn.reg32()) = temp;
+}
+
+void SoftCPU::XCHG_reg8_RM8(const X86::Instruction& insn)
+{
+ auto temp = insn.modrm().read8(*this, insn);
+ insn.modrm().write8(*this, insn, const_gpr8(insn.reg8()));
+ gpr8(insn.reg8()) = temp;
+}
+
+void SoftCPU::XLAT(const X86::Instruction& insn)
+{
+ if (insn.a32())
+ warn_if_uninitialized(ebx(), "xlat ebx");
+ else
+ warn_if_uninitialized(bx(), "xlat bx");
+ warn_if_uninitialized(al(), "xlat al");
+ u32 offset = (insn.a32() ? ebx().value() : bx().value()) + al().value();
+ set_al(read_memory8({ segment(insn.segment_prefix().value_or(X86::SegmentRegister::DS)), offset }));
+}
+
+#define DEFINE_GENERIC_INSN_HANDLERS_PARTIAL(mnemonic, op, update_dest, is_zero_idiom_if_both_operands_same, is_or) \
+ void SoftCPU::mnemonic##_AL_imm8(const X86::Instruction& insn) { generic_AL_imm8<update_dest, is_or>(op<ValueWithShadow<u8>>, insn); } \
+ void SoftCPU::mnemonic##_AX_imm16(const X86::Instruction& insn) { generic_AX_imm16<update_dest, is_or>(op<ValueWithShadow<u16>>, insn); } \
+ void SoftCPU::mnemonic##_EAX_imm32(const X86::Instruction& insn) { generic_EAX_imm32<update_dest, is_or>(op<ValueWithShadow<u32>>, insn); } \
+ void SoftCPU::mnemonic##_RM16_imm16(const X86::Instruction& insn) { generic_RM16_imm16<update_dest, is_or>(op<ValueWithShadow<u16>>, insn); } \
+ void SoftCPU::mnemonic##_RM16_reg16(const X86::Instruction& insn) { generic_RM16_reg16<update_dest, is_zero_idiom_if_both_operands_same>(op<ValueWithShadow<u16>>, insn); } \
+ void SoftCPU::mnemonic##_RM32_imm32(const X86::Instruction& insn) { generic_RM32_imm32<update_dest, is_or>(op<ValueWithShadow<u32>>, insn); } \
+ void SoftCPU::mnemonic##_RM32_reg32(const X86::Instruction& insn) { generic_RM32_reg32<update_dest, is_zero_idiom_if_both_operands_same>(op<ValueWithShadow<u32>>, insn); } \
+ void SoftCPU::mnemonic##_RM8_imm8(const X86::Instruction& insn) { generic_RM8_imm8<update_dest, is_or>(op<ValueWithShadow<u8>>, insn); } \
+ void SoftCPU::mnemonic##_RM8_reg8(const X86::Instruction& insn) { generic_RM8_reg8<update_dest, is_zero_idiom_if_both_operands_same>(op<ValueWithShadow<u8>>, insn); }
+
+#define DEFINE_GENERIC_INSN_HANDLERS(mnemonic, op, update_dest, is_zero_idiom_if_both_operands_same, is_or) \
+ DEFINE_GENERIC_INSN_HANDLERS_PARTIAL(mnemonic, op, update_dest, is_zero_idiom_if_both_operands_same, is_or) \
+ void SoftCPU::mnemonic##_RM16_imm8(const X86::Instruction& insn) { generic_RM16_imm8<update_dest, is_or>(op<ValueWithShadow<u16>>, insn); } \
+ void SoftCPU::mnemonic##_RM32_imm8(const X86::Instruction& insn) { generic_RM32_imm8<update_dest, is_or>(op<ValueWithShadow<u32>>, insn); } \
+ void SoftCPU::mnemonic##_reg16_RM16(const X86::Instruction& insn) { generic_reg16_RM16<update_dest, is_zero_idiom_if_both_operands_same>(op<ValueWithShadow<u16>>, insn); } \
+ void SoftCPU::mnemonic##_reg32_RM32(const X86::Instruction& insn) { generic_reg32_RM32<update_dest, is_zero_idiom_if_both_operands_same>(op<ValueWithShadow<u32>>, insn); } \
+ void SoftCPU::mnemonic##_reg8_RM8(const X86::Instruction& insn) { generic_reg8_RM8<update_dest, is_zero_idiom_if_both_operands_same>(op<ValueWithShadow<u8>>, insn); }
+
+DEFINE_GENERIC_INSN_HANDLERS(XOR, op_xor, true, true, false)
+DEFINE_GENERIC_INSN_HANDLERS(OR, op_or, true, false, true)
+DEFINE_GENERIC_INSN_HANDLERS(ADD, op_add, true, false, false)
+DEFINE_GENERIC_INSN_HANDLERS(ADC, op_adc, true, false, false)
+DEFINE_GENERIC_INSN_HANDLERS(SUB, op_sub, true, true, false)
+DEFINE_GENERIC_INSN_HANDLERS(SBB, op_sbb, true, false, false)
+DEFINE_GENERIC_INSN_HANDLERS(AND, op_and, true, false, false)
+DEFINE_GENERIC_INSN_HANDLERS(CMP, op_sub, false, false, false)
+DEFINE_GENERIC_INSN_HANDLERS_PARTIAL(TEST, op_and, false, false, false)
+
+void SoftCPU::MOVQ_mm1_mm2m64(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::EMMS(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::MOVQ_mm1_m64_mm2(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::wrap_0xC0(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::wrap_0xC1_16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::wrap_0xC1_32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::wrap_0xD0(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::wrap_0xD1_16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::wrap_0xD1_32(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::wrap_0xD2(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::wrap_0xD3_16(const X86::Instruction&) { TODO_INSN(); }
+void SoftCPU::wrap_0xD3_32(const X86::Instruction&) { TODO_INSN(); }
+}
diff --git a/Userland/DevTools/UserspaceEmulator/SoftCPU.h b/Userland/DevTools/UserspaceEmulator/SoftCPU.h
new file mode 100644
index 0000000000..89ece8976d
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/SoftCPU.h
@@ -0,0 +1,1202 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Region.h"
+#include "ValueWithShadow.h"
+#include <LibX86/Instruction.h>
+#include <LibX86/Interpreter.h>
+
+namespace UserspaceEmulator {
+
+class Emulator;
+class Region;
+
+union PartAddressableRegister {
+ struct {
+ u32 full_u32;
+ };
+ struct {
+ u16 low_u16;
+ u16 high_u16;
+ };
+ struct {
+ u8 low_u8;
+ u8 high_u8;
+ u16 also_high_u16;
+ };
+};
+
+class SoftCPU final
+ : public X86::Interpreter
+ , public X86::InstructionStream {
+public:
+ using ValueWithShadowType8 = ValueWithShadow<u8>;
+ using ValueWithShadowType16 = ValueWithShadow<u16>;
+ using ValueWithShadowType32 = ValueWithShadow<u32>;
+ using ValueWithShadowType64 = ValueWithShadow<u64>;
+
+ explicit SoftCPU(Emulator&);
+ void dump() const;
+
+ u32 base_eip() const { return m_base_eip; }
+ void save_base_eip() { m_base_eip = m_eip; }
+
+ u32 eip() const { return m_eip; }
+ void set_eip(u32 eip)
+ {
+ m_eip = eip;
+ }
+
+ struct Flags {
+ enum Flag {
+ CF = 0x0001,
+ PF = 0x0004,
+ AF = 0x0010,
+ ZF = 0x0040,
+ SF = 0x0080,
+ TF = 0x0100,
+ IF = 0x0200,
+ DF = 0x0400,
+ OF = 0x0800,
+ };
+ };
+
+ void push32(ValueWithShadow<u32>);
+ ValueWithShadow<u32> pop32();
+
+ void push16(ValueWithShadow<u16>);
+ ValueWithShadow<u16> pop16();
+
+ void push_string(const StringView&);
+ void push_buffer(const u8* data, size_t);
+
+ u16 segment(X86::SegmentRegister seg) const { return m_segment[(int)seg]; }
+ u16& segment(X86::SegmentRegister seg) { return m_segment[(int)seg]; }
+
+ ValueAndShadowReference<u8> gpr8(X86::RegisterIndex8 reg)
+ {
+ switch (reg) {
+ case X86::RegisterAL:
+ return { m_gpr[X86::RegisterEAX].low_u8, m_gpr_shadow[X86::RegisterEAX].low_u8 };
+ case X86::RegisterAH:
+ return { m_gpr[X86::RegisterEAX].high_u8, m_gpr_shadow[X86::RegisterEAX].high_u8 };
+ case X86::RegisterBL:
+ return { m_gpr[X86::RegisterEBX].low_u8, m_gpr_shadow[X86::RegisterEBX].low_u8 };
+ case X86::RegisterBH:
+ return { m_gpr[X86::RegisterEBX].high_u8, m_gpr_shadow[X86::RegisterEBX].high_u8 };
+ case X86::RegisterCL:
+ return { m_gpr[X86::RegisterECX].low_u8, m_gpr_shadow[X86::RegisterECX].low_u8 };
+ case X86::RegisterCH:
+ return { m_gpr[X86::RegisterECX].high_u8, m_gpr_shadow[X86::RegisterECX].high_u8 };
+ case X86::RegisterDL:
+ return { m_gpr[X86::RegisterEDX].low_u8, m_gpr_shadow[X86::RegisterEDX].low_u8 };
+ case X86::RegisterDH:
+ return { m_gpr[X86::RegisterEDX].high_u8, m_gpr_shadow[X86::RegisterEDX].high_u8 };
+ }
+ ASSERT_NOT_REACHED();
+ }
+
+ ValueWithShadow<u8> const_gpr8(X86::RegisterIndex8 reg) const
+ {
+ switch (reg) {
+ case X86::RegisterAL:
+ return { m_gpr[X86::RegisterEAX].low_u8, m_gpr_shadow[X86::RegisterEAX].low_u8 };
+ case X86::RegisterAH:
+ return { m_gpr[X86::RegisterEAX].high_u8, m_gpr_shadow[X86::RegisterEAX].high_u8 };
+ case X86::RegisterBL:
+ return { m_gpr[X86::RegisterEBX].low_u8, m_gpr_shadow[X86::RegisterEBX].low_u8 };
+ case X86::RegisterBH:
+ return { m_gpr[X86::RegisterEBX].high_u8, m_gpr_shadow[X86::RegisterEBX].high_u8 };
+ case X86::RegisterCL:
+ return { m_gpr[X86::RegisterECX].low_u8, m_gpr_shadow[X86::RegisterECX].low_u8 };
+ case X86::RegisterCH:
+ return { m_gpr[X86::RegisterECX].high_u8, m_gpr_shadow[X86::RegisterECX].high_u8 };
+ case X86::RegisterDL:
+ return { m_gpr[X86::RegisterEDX].low_u8, m_gpr_shadow[X86::RegisterEDX].low_u8 };
+ case X86::RegisterDH:
+ return { m_gpr[X86::RegisterEDX].high_u8, m_gpr_shadow[X86::RegisterEDX].high_u8 };
+ }
+ ASSERT_NOT_REACHED();
+ }
+
+ ValueWithShadow<u16> const_gpr16(X86::RegisterIndex16 reg) const
+ {
+ return { m_gpr[reg].low_u16, m_gpr_shadow[reg].low_u16 };
+ }
+
+ ValueAndShadowReference<u16> gpr16(X86::RegisterIndex16 reg)
+ {
+ return { m_gpr[reg].low_u16, m_gpr_shadow[reg].low_u16 };
+ }
+
+ ValueWithShadow<u32> const_gpr32(X86::RegisterIndex32 reg) const
+ {
+ return { m_gpr[reg].full_u32, m_gpr_shadow[reg].full_u32 };
+ }
+
+ ValueAndShadowReference<u32> gpr32(X86::RegisterIndex32 reg)
+ {
+ return { m_gpr[reg].full_u32, m_gpr_shadow[reg].full_u32 };
+ }
+
+ template<typename T>
+ ValueWithShadow<T> const_gpr(unsigned register_index) const
+ {
+ if constexpr (sizeof(T) == 1)
+ return const_gpr8((X86::RegisterIndex8)register_index);
+ if constexpr (sizeof(T) == 2)
+ return const_gpr16((X86::RegisterIndex16)register_index);
+ if constexpr (sizeof(T) == 4)
+ return const_gpr32((X86::RegisterIndex32)register_index);
+ }
+
+ template<typename T>
+ ValueAndShadowReference<T> gpr(unsigned register_index)
+ {
+ if constexpr (sizeof(T) == 1)
+ return gpr8((X86::RegisterIndex8)register_index);
+ if constexpr (sizeof(T) == 2)
+ return gpr16((X86::RegisterIndex16)register_index);
+ if constexpr (sizeof(T) == 4)
+ return gpr32((X86::RegisterIndex32)register_index);
+ }
+
+ ValueWithShadow<u32> source_index(bool a32) const
+ {
+ if (a32)
+ return esi();
+ return { si().value(), (u32)si().shadow() & 0xffff };
+ }
+
+ ValueWithShadow<u32> destination_index(bool a32) const
+ {
+ if (a32)
+ return edi();
+ return { di().value(), (u32)di().shadow() & 0xffff };
+ }
+
+ ValueWithShadow<u32> loop_index(bool a32) const
+ {
+ if (a32)
+ return ecx();
+ return { cx().value(), (u32)cx().shadow() & 0xffff };
+ }
+
+ bool decrement_loop_index(bool a32)
+ {
+ if (a32) {
+ set_ecx({ ecx().value() - 1, ecx().shadow() });
+ return ecx().value() == 0;
+ }
+ set_cx(ValueWithShadow<u16>(cx().value() - 1, cx().shadow()));
+ return cx().value() == 0;
+ }
+
+ ALWAYS_INLINE void step_source_index(bool a32, u32 step)
+ {
+ if (a32) {
+ if (df())
+ set_esi({ esi().value() - step, esi().shadow() });
+ else
+ set_esi({ esi().value() + step, esi().shadow() });
+ } else {
+ if (df())
+ set_si(ValueWithShadow<u16>(si().value() - step, si().shadow()));
+ else
+ set_si(ValueWithShadow<u16>(si().value() + step, si().shadow()));
+ }
+ }
+
+ ALWAYS_INLINE void step_destination_index(bool a32, u32 step)
+ {
+ if (a32) {
+ if (df())
+ set_edi({ edi().value() - step, edi().shadow() });
+ else
+ set_edi({ edi().value() + step, edi().shadow() });
+ } else {
+ if (df())
+ set_di(ValueWithShadow<u16>(di().value() - step, di().shadow()));
+ else
+ set_di(ValueWithShadow<u16>(di().value() + step, di().shadow()));
+ }
+ }
+
+ u32 eflags() const { return m_eflags; }
+ void set_eflags(ValueWithShadow<u32> eflags)
+ {
+ m_eflags = eflags.value();
+ m_flags_tainted = eflags.is_uninitialized();
+ }
+
+ ValueWithShadow<u32> eax() const { return const_gpr32(X86::RegisterEAX); }
+ ValueWithShadow<u32> ebx() const { return const_gpr32(X86::RegisterEBX); }
+ ValueWithShadow<u32> ecx() const { return const_gpr32(X86::RegisterECX); }
+ ValueWithShadow<u32> edx() const { return const_gpr32(X86::RegisterEDX); }
+ ValueWithShadow<u32> esp() const { return const_gpr32(X86::RegisterESP); }
+ ValueWithShadow<u32> ebp() const { return const_gpr32(X86::RegisterEBP); }
+ ValueWithShadow<u32> esi() const { return const_gpr32(X86::RegisterESI); }
+ ValueWithShadow<u32> edi() const { return const_gpr32(X86::RegisterEDI); }
+
+ ValueWithShadow<u16> ax() const { return const_gpr16(X86::RegisterAX); }
+ ValueWithShadow<u16> bx() const { return const_gpr16(X86::RegisterBX); }
+ ValueWithShadow<u16> cx() const { return const_gpr16(X86::RegisterCX); }
+ ValueWithShadow<u16> dx() const { return const_gpr16(X86::RegisterDX); }
+ ValueWithShadow<u16> sp() const { return const_gpr16(X86::RegisterSP); }
+ ValueWithShadow<u16> bp() const { return const_gpr16(X86::RegisterBP); }
+ ValueWithShadow<u16> si() const { return const_gpr16(X86::RegisterSI); }
+ ValueWithShadow<u16> di() const { return const_gpr16(X86::RegisterDI); }
+
+ ValueWithShadow<u8> al() const { return const_gpr8(X86::RegisterAL); }
+ ValueWithShadow<u8> ah() const { return const_gpr8(X86::RegisterAH); }
+ ValueWithShadow<u8> bl() const { return const_gpr8(X86::RegisterBL); }
+ ValueWithShadow<u8> bh() const { return const_gpr8(X86::RegisterBH); }
+ ValueWithShadow<u8> cl() const { return const_gpr8(X86::RegisterCL); }
+ ValueWithShadow<u8> ch() const { return const_gpr8(X86::RegisterCH); }
+ ValueWithShadow<u8> dl() const { return const_gpr8(X86::RegisterDL); }
+ ValueWithShadow<u8> dh() const { return const_gpr8(X86::RegisterDH); }
+
+ void set_eax(ValueWithShadow<u32> value) { gpr32(X86::RegisterEAX) = value; }
+ void set_ebx(ValueWithShadow<u32> value) { gpr32(X86::RegisterEBX) = value; }
+ void set_ecx(ValueWithShadow<u32> value) { gpr32(X86::RegisterECX) = value; }
+ void set_edx(ValueWithShadow<u32> value) { gpr32(X86::RegisterEDX) = value; }
+ void set_esp(ValueWithShadow<u32> value) { gpr32(X86::RegisterESP) = value; }
+ void set_ebp(ValueWithShadow<u32> value) { gpr32(X86::RegisterEBP) = value; }
+ void set_esi(ValueWithShadow<u32> value) { gpr32(X86::RegisterESI) = value; }
+ void set_edi(ValueWithShadow<u32> value) { gpr32(X86::RegisterEDI) = value; }
+
+ void set_ax(ValueWithShadow<u16> value) { gpr16(X86::RegisterAX) = value; }
+ void set_bx(ValueWithShadow<u16> value) { gpr16(X86::RegisterBX) = value; }
+ void set_cx(ValueWithShadow<u16> value) { gpr16(X86::RegisterCX) = value; }
+ void set_dx(ValueWithShadow<u16> value) { gpr16(X86::RegisterDX) = value; }
+ void set_sp(ValueWithShadow<u16> value) { gpr16(X86::RegisterSP) = value; }
+ void set_bp(ValueWithShadow<u16> value) { gpr16(X86::RegisterBP) = value; }
+ void set_si(ValueWithShadow<u16> value) { gpr16(X86::RegisterSI) = value; }
+ void set_di(ValueWithShadow<u16> value) { gpr16(X86::RegisterDI) = value; }
+
+ void set_al(ValueWithShadow<u8> value) { gpr8(X86::RegisterAL) = value; }
+ void set_ah(ValueWithShadow<u8> value) { gpr8(X86::RegisterAH) = value; }
+ void set_bl(ValueWithShadow<u8> value) { gpr8(X86::RegisterBL) = value; }
+ void set_bh(ValueWithShadow<u8> value) { gpr8(X86::RegisterBH) = value; }
+ void set_cl(ValueWithShadow<u8> value) { gpr8(X86::RegisterCL) = value; }
+ void set_ch(ValueWithShadow<u8> value) { gpr8(X86::RegisterCH) = value; }
+ void set_dl(ValueWithShadow<u8> value) { gpr8(X86::RegisterDL) = value; }
+ void set_dh(ValueWithShadow<u8> value) { gpr8(X86::RegisterDH) = value; }
+
+ bool of() const { return m_eflags & Flags::OF; }
+ bool sf() const { return m_eflags & Flags::SF; }
+ bool zf() const { return m_eflags & Flags::ZF; }
+ bool af() const { return m_eflags & Flags::AF; }
+ bool pf() const { return m_eflags & Flags::PF; }
+ bool cf() const { return m_eflags & Flags::CF; }
+ bool df() const { return m_eflags & Flags::DF; }
+
+ void set_flag(Flags::Flag flag, bool value)
+ {
+ if (value)
+ m_eflags |= flag;
+ else
+ m_eflags &= ~flag;
+ }
+
+ void set_of(bool value) { set_flag(Flags::OF, value); }
+ void set_sf(bool value) { set_flag(Flags::SF, value); }
+ void set_zf(bool value) { set_flag(Flags::ZF, value); }
+ void set_af(bool value) { set_flag(Flags::AF, value); }
+ void set_pf(bool value) { set_flag(Flags::PF, value); }
+ void set_cf(bool value) { set_flag(Flags::CF, value); }
+ void set_df(bool value) { set_flag(Flags::DF, value); }
+
+ void set_flags_with_mask(u32 new_flags, u32 mask)
+ {
+ m_eflags &= ~mask;
+ m_eflags |= new_flags & mask;
+ }
+
+ void set_flags_oszapc(u32 new_flags)
+ {
+ set_flags_with_mask(new_flags, Flags::OF | Flags::SF | Flags::ZF | Flags::AF | Flags::PF | Flags::CF);
+ }
+
+ void set_flags_oszap(u32 new_flags)
+ {
+ set_flags_with_mask(new_flags, Flags::OF | Flags::SF | Flags::ZF | Flags::AF | Flags::PF);
+ }
+
+ void set_flags_oszpc(u32 new_flags)
+ {
+ set_flags_with_mask(new_flags, Flags::OF | Flags::SF | Flags::ZF | Flags::PF | Flags::CF);
+ }
+
+ void set_flags_oc(u32 new_flags)
+ {
+ set_flags_with_mask(new_flags, Flags::OF | Flags::CF);
+ }
+
+ u16 cs() const { return m_segment[(int)X86::SegmentRegister::CS]; }
+ u16 ds() const { return m_segment[(int)X86::SegmentRegister::DS]; }
+ u16 es() const { return m_segment[(int)X86::SegmentRegister::ES]; }
+ u16 ss() const { return m_segment[(int)X86::SegmentRegister::SS]; }
+
+ ValueWithShadow<u8> read_memory8(X86::LogicalAddress);
+ ValueWithShadow<u16> read_memory16(X86::LogicalAddress);
+ ValueWithShadow<u32> read_memory32(X86::LogicalAddress);
+ ValueWithShadow<u64> read_memory64(X86::LogicalAddress);
+
+ template<typename T>
+ ValueWithShadow<T> read_memory(X86::LogicalAddress address)
+ {
+ if constexpr (sizeof(T) == 1)
+ return read_memory8(address);
+ if constexpr (sizeof(T) == 2)
+ return read_memory16(address);
+ if constexpr (sizeof(T) == 4)
+ return read_memory32(address);
+ }
+
+ void write_memory8(X86::LogicalAddress, ValueWithShadow<u8>);
+ void write_memory16(X86::LogicalAddress, ValueWithShadow<u16>);
+ void write_memory32(X86::LogicalAddress, ValueWithShadow<u32>);
+ void write_memory64(X86::LogicalAddress, ValueWithShadow<u64>);
+
+ template<typename T>
+ void write_memory(X86::LogicalAddress address, ValueWithShadow<T> data)
+ {
+ if constexpr (sizeof(T) == 1)
+ return write_memory8(address, data);
+ if constexpr (sizeof(T) == 2)
+ return write_memory16(address, data);
+ if constexpr (sizeof(T) == 4)
+ return write_memory32(address, data);
+ }
+
+ bool evaluate_condition(u8 condition) const
+ {
+ switch (condition) {
+ case 0:
+ return of(); // O
+ case 1:
+ return !of(); // NO
+ case 2:
+ return cf(); // B, C, NAE
+ case 3:
+ return !cf(); // NB, NC, AE
+ case 4:
+ return zf(); // E, Z
+ case 5:
+ return !zf(); // NE, NZ
+ case 6:
+ return (cf() | zf()); // BE, NA
+ case 7:
+ return !(cf() | zf()); // NBE, A
+ case 8:
+ return sf(); // S
+ case 9:
+ return !sf(); // NS
+ case 10:
+ return pf(); // P, PE
+ case 11:
+ return !pf(); // NP, PO
+ case 12:
+ return sf() ^ of(); // L, NGE
+ case 13:
+ return !(sf() ^ of()); // NL, GE
+ case 14:
+ return (sf() ^ of()) | zf(); // LE, NG
+ case 15:
+ return !((sf() ^ of()) | zf()); // NLE, G
+ default:
+ ASSERT_NOT_REACHED();
+ }
+ return 0;
+ }
+
+ template<bool check_zf, typename Callback>
+ void do_once_or_repeat(const X86::Instruction& insn, Callback);
+
+ template<typename A>
+ void taint_flags_from(const A& a)
+ {
+ m_flags_tainted = a.is_uninitialized();
+ }
+
+ template<typename A, typename B>
+ void taint_flags_from(const A& a, const B& b)
+ {
+ m_flags_tainted = a.is_uninitialized() || b.is_uninitialized();
+ }
+
+ template<typename A, typename B, typename C>
+ void taint_flags_from(const A& a, const B& b, const C& c)
+ {
+ m_flags_tainted = a.is_uninitialized() || b.is_uninitialized() || c.is_uninitialized();
+ }
+
+ void warn_if_flags_tainted(const char* message) const;
+
+ // ^X86::InstructionStream
+ virtual bool can_read() override { return false; }
+ virtual u8 read8() override;
+ virtual u16 read16() override;
+ virtual u32 read32() override;
+ virtual u64 read64() override;
+
+private:
+ // ^X86::Interpreter
+ virtual void AAA(const X86::Instruction&) override;
+ virtual void AAD(const X86::Instruction&) override;
+ virtual void AAM(const X86::Instruction&) override;
+ virtual void AAS(const X86::Instruction&) override;
+ virtual void ADC_AL_imm8(const X86::Instruction&) override;
+ virtual void ADC_AX_imm16(const X86::Instruction&) override;
+ virtual void ADC_EAX_imm32(const X86::Instruction&) override;
+ virtual void ADC_RM16_imm16(const X86::Instruction&) override;
+ virtual void ADC_RM16_imm8(const X86::Instruction&) override;
+ virtual void ADC_RM16_reg16(const X86::Instruction&) override;
+ virtual void ADC_RM32_imm32(const X86::Instruction&) override;
+ virtual void ADC_RM32_imm8(const X86::Instruction&) override;
+ virtual void ADC_RM32_reg32(const X86::Instruction&) override;
+ virtual void ADC_RM8_imm8(const X86::Instruction&) override;
+ virtual void ADC_RM8_reg8(const X86::Instruction&) override;
+ virtual void ADC_reg16_RM16(const X86::Instruction&) override;
+ virtual void ADC_reg32_RM32(const X86::Instruction&) override;
+ virtual void ADC_reg8_RM8(const X86::Instruction&) override;
+ virtual void ADD_AL_imm8(const X86::Instruction&) override;
+ virtual void ADD_AX_imm16(const X86::Instruction&) override;
+ virtual void ADD_EAX_imm32(const X86::Instruction&) override;
+ virtual void ADD_RM16_imm16(const X86::Instruction&) override;
+ virtual void ADD_RM16_imm8(const X86::Instruction&) override;
+ virtual void ADD_RM16_reg16(const X86::Instruction&) override;
+ virtual void ADD_RM32_imm32(const X86::Instruction&) override;
+ virtual void ADD_RM32_imm8(const X86::Instruction&) override;
+ virtual void ADD_RM32_reg32(const X86::Instruction&) override;
+ virtual void ADD_RM8_imm8(const X86::Instruction&) override;
+ virtual void ADD_RM8_reg8(const X86::Instruction&) override;
+ virtual void ADD_reg16_RM16(const X86::Instruction&) override;
+ virtual void ADD_reg32_RM32(const X86::Instruction&) override;
+ virtual void ADD_reg8_RM8(const X86::Instruction&) override;
+ virtual void AND_AL_imm8(const X86::Instruction&) override;
+ virtual void AND_AX_imm16(const X86::Instruction&) override;
+ virtual void AND_EAX_imm32(const X86::Instruction&) override;
+ virtual void AND_RM16_imm16(const X86::Instruction&) override;
+ virtual void AND_RM16_imm8(const X86::Instruction&) override;
+ virtual void AND_RM16_reg16(const X86::Instruction&) override;
+ virtual void AND_RM32_imm32(const X86::Instruction&) override;
+ virtual void AND_RM32_imm8(const X86::Instruction&) override;
+ virtual void AND_RM32_reg32(const X86::Instruction&) override;
+ virtual void AND_RM8_imm8(const X86::Instruction&) override;
+ virtual void AND_RM8_reg8(const X86::Instruction&) override;
+ virtual void AND_reg16_RM16(const X86::Instruction&) override;
+ virtual void AND_reg32_RM32(const X86::Instruction&) override;
+ virtual void AND_reg8_RM8(const X86::Instruction&) override;
+ virtual void ARPL(const X86::Instruction&) override;
+ virtual void BOUND(const X86::Instruction&) override;
+ virtual void BSF_reg16_RM16(const X86::Instruction&) override;
+ virtual void BSF_reg32_RM32(const X86::Instruction&) override;
+ virtual void BSR_reg16_RM16(const X86::Instruction&) override;
+ virtual void BSR_reg32_RM32(const X86::Instruction&) override;
+ virtual void BSWAP_reg32(const X86::Instruction&) override;
+ virtual void BTC_RM16_imm8(const X86::Instruction&) override;
+ virtual void BTC_RM16_reg16(const X86::Instruction&) override;
+ virtual void BTC_RM32_imm8(const X86::Instruction&) override;
+ virtual void BTC_RM32_reg32(const X86::Instruction&) override;
+ virtual void BTR_RM16_imm8(const X86::Instruction&) override;
+ virtual void BTR_RM16_reg16(const X86::Instruction&) override;
+ virtual void BTR_RM32_imm8(const X86::Instruction&) override;
+ virtual void BTR_RM32_reg32(const X86::Instruction&) override;
+ virtual void BTS_RM16_imm8(const X86::Instruction&) override;
+ virtual void BTS_RM16_reg16(const X86::Instruction&) override;
+ virtual void BTS_RM32_imm8(const X86::Instruction&) override;
+ virtual void BTS_RM32_reg32(const X86::Instruction&) override;
+ virtual void BT_RM16_imm8(const X86::Instruction&) override;
+ virtual void BT_RM16_reg16(const X86::Instruction&) override;
+ virtual void BT_RM32_imm8(const X86::Instruction&) override;
+ virtual void BT_RM32_reg32(const X86::Instruction&) override;
+ virtual void CALL_FAR_mem16(const X86::Instruction&) override;
+ virtual void CALL_FAR_mem32(const X86::Instruction&) override;
+ virtual void CALL_RM16(const X86::Instruction&) override;
+ virtual void CALL_RM32(const X86::Instruction&) override;
+ virtual void CALL_imm16(const X86::Instruction&) override;
+ virtual void CALL_imm16_imm16(const X86::Instruction&) override;
+ virtual void CALL_imm16_imm32(const X86::Instruction&) override;
+ virtual void CALL_imm32(const X86::Instruction&) override;
+ virtual void CBW(const X86::Instruction&) override;
+ virtual void CDQ(const X86::Instruction&) override;
+ virtual void CLC(const X86::Instruction&) override;
+ virtual void CLD(const X86::Instruction&) override;
+ virtual void CLI(const X86::Instruction&) override;
+ virtual void CLTS(const X86::Instruction&) override;
+ virtual void CMC(const X86::Instruction&) override;
+ virtual void CMOVcc_reg16_RM16(const X86::Instruction&) override;
+ virtual void CMOVcc_reg32_RM32(const X86::Instruction&) override;
+ virtual void CMPSB(const X86::Instruction&) override;
+ virtual void CMPSD(const X86::Instruction&) override;
+ virtual void CMPSW(const X86::Instruction&) override;
+ virtual void CMPXCHG_RM16_reg16(const X86::Instruction&) override;
+ virtual void CMPXCHG_RM32_reg32(const X86::Instruction&) override;
+ virtual void CMPXCHG_RM8_reg8(const X86::Instruction&) override;
+ virtual void CMP_AL_imm8(const X86::Instruction&) override;
+ virtual void CMP_AX_imm16(const X86::Instruction&) override;
+ virtual void CMP_EAX_imm32(const X86::Instruction&) override;
+ virtual void CMP_RM16_imm16(const X86::Instruction&) override;
+ virtual void CMP_RM16_imm8(const X86::Instruction&) override;
+ virtual void CMP_RM16_reg16(const X86::Instruction&) override;
+ virtual void CMP_RM32_imm32(const X86::Instruction&) override;
+ virtual void CMP_RM32_imm8(const X86::Instruction&) override;
+ virtual void CMP_RM32_reg32(const X86::Instruction&) override;
+ virtual void CMP_RM8_imm8(const X86::Instruction&) override;
+ virtual void CMP_RM8_reg8(const X86::Instruction&) override;
+ virtual void CMP_reg16_RM16(const X86::Instruction&) override;
+ virtual void CMP_reg32_RM32(const X86::Instruction&) override;
+ virtual void CMP_reg8_RM8(const X86::Instruction&) override;
+ virtual void CPUID(const X86::Instruction&) override;
+ virtual void CWD(const X86::Instruction&) override;
+ virtual void CWDE(const X86::Instruction&) override;
+ virtual void DAA(const X86::Instruction&) override;
+ virtual void DAS(const X86::Instruction&) override;
+ virtual void DEC_RM16(const X86::Instruction&) override;
+ virtual void DEC_RM32(const X86::Instruction&) override;
+ virtual void DEC_RM8(const X86::Instruction&) override;
+ virtual void DEC_reg16(const X86::Instruction&) override;
+ virtual void DEC_reg32(const X86::Instruction&) override;
+ virtual void DIV_RM16(const X86::Instruction&) override;
+ virtual void DIV_RM32(const X86::Instruction&) override;
+ virtual void DIV_RM8(const X86::Instruction&) override;
+ virtual void ENTER16(const X86::Instruction&) override;
+ virtual void ENTER32(const X86::Instruction&) override;
+ virtual void ESCAPE(const X86::Instruction&) override;
+ virtual void FADD_RM32(const X86::Instruction&) override;
+ virtual void FMUL_RM32(const X86::Instruction&) override;
+ virtual void FCOM_RM32(const X86::Instruction&) override;
+ virtual void FCOMP_RM32(const X86::Instruction&) override;
+ virtual void FSUB_RM32(const X86::Instruction&) override;
+ virtual void FSUBR_RM32(const X86::Instruction&) override;
+ virtual void FDIV_RM32(const X86::Instruction&) override;
+ virtual void FDIVR_RM32(const X86::Instruction&) override;
+ virtual void FLD_RM32(const X86::Instruction&) override;
+ virtual void FXCH(const X86::Instruction&) override;
+ virtual void FST_RM32(const X86::Instruction&) override;
+ virtual void FNOP(const X86::Instruction&) override;
+ virtual void FSTP_RM32(const X86::Instruction&) override;
+ virtual void FLDENV(const X86::Instruction&) override;
+ virtual void FCHS(const X86::Instruction&) override;
+ virtual void FABS(const X86::Instruction&) override;
+ virtual void FTST(const X86::Instruction&) override;
+ virtual void FXAM(const X86::Instruction&) override;
+ virtual void FLDCW(const X86::Instruction&) override;
+ virtual void FLD1(const X86::Instruction&) override;
+ virtual void FLDL2T(const X86::Instruction&) override;
+ virtual void FLDL2E(const X86::Instruction&) override;
+ virtual void FLDPI(const X86::Instruction&) override;
+ virtual void FLDLG2(const X86::Instruction&) override;
+ virtual void FLDLN2(const X86::Instruction&) override;
+ virtual void FLDZ(const X86::Instruction&) override;
+ virtual void FNSTENV(const X86::Instruction&) override;
+ virtual void F2XM1(const X86::Instruction&) override;
+ virtual void FYL2X(const X86::Instruction&) override;
+ virtual void FPTAN(const X86::Instruction&) override;
+ virtual void FPATAN(const X86::Instruction&) override;
+ virtual void FXTRACT(const X86::Instruction&) override;
+ virtual void FPREM1(const X86::Instruction&) override;
+ virtual void FDECSTP(const X86::Instruction&) override;
+ virtual void FINCSTP(const X86::Instruction&) override;
+ virtual void FNSTCW(const X86::Instruction&) override;
+ virtual void FPREM(const X86::Instruction&) override;
+ virtual void FYL2XP1(const X86::Instruction&) override;
+ virtual void FSQRT(const X86::Instruction&) override;
+ virtual void FSINCOS(const X86::Instruction&) override;
+ virtual void FRNDINT(const X86::Instruction&) override;
+ virtual void FSCALE(const X86::Instruction&) override;
+ virtual void FSIN(const X86::Instruction&) override;
+ virtual void FCOS(const X86::Instruction&) override;
+ virtual void FIADD_RM32(const X86::Instruction&) override;
+ virtual void FCMOVB(const X86::Instruction&) override;
+ virtual void FIMUL_RM32(const X86::Instruction&) override;
+ virtual void FCMOVE(const X86::Instruction&) override;
+ virtual void FICOM_RM32(const X86::Instruction&) override;
+ virtual void FCMOVBE(const X86::Instruction&) override;
+ virtual void FICOMP_RM32(const X86::Instruction&) override;
+ virtual void FCMOVU(const X86::Instruction&) override;
+ virtual void FISUB_RM32(const X86::Instruction&) override;
+ virtual void FISUBR_RM32(const X86::Instruction&) override;
+ virtual void FUCOMPP(const X86::Instruction&) override;
+ virtual void FIDIV_RM32(const X86::Instruction&) override;
+ virtual void FIDIVR_RM32(const X86::Instruction&) override;
+ virtual void FILD_RM32(const X86::Instruction&) override;
+ virtual void FCMOVNB(const X86::Instruction&) override;
+ virtual void FISTTP_RM32(const X86::Instruction&) override;
+ virtual void FCMOVNE(const X86::Instruction&) override;
+ virtual void FIST_RM32(const X86::Instruction&) override;
+ virtual void FCMOVNBE(const X86::Instruction&) override;
+ virtual void FISTP_RM32(const X86::Instruction&) override;
+ virtual void FCMOVNU(const X86::Instruction&) override;
+ virtual void FNENI(const X86::Instruction&) override;
+ virtual void FNDISI(const X86::Instruction&) override;
+ virtual void FNCLEX(const X86::Instruction&) override;
+ virtual void FNINIT(const X86::Instruction&) override;
+ virtual void FNSETPM(const X86::Instruction&) override;
+ virtual void FLD_RM80(const X86::Instruction&) override;
+ virtual void FUCOMI(const X86::Instruction&) override;
+ virtual void FCOMI(const X86::Instruction&) override;
+ virtual void FSTP_RM80(const X86::Instruction&) override;
+ virtual void FADD_RM64(const X86::Instruction&) override;
+ virtual void FMUL_RM64(const X86::Instruction&) override;
+ virtual void FCOM_RM64(const X86::Instruction&) override;
+ virtual void FCOMP_RM64(const X86::Instruction&) override;
+ virtual void FSUB_RM64(const X86::Instruction&) override;
+ virtual void FSUBR_RM64(const X86::Instruction&) override;
+ virtual void FDIV_RM64(const X86::Instruction&) override;
+ virtual void FDIVR_RM64(const X86::Instruction&) override;
+ virtual void FLD_RM64(const X86::Instruction&) override;
+ virtual void FFREE(const X86::Instruction&) override;
+ virtual void FISTTP_RM64(const X86::Instruction&) override;
+ virtual void FST_RM64(const X86::Instruction&) override;
+ virtual void FSTP_RM64(const X86::Instruction&) override;
+ virtual void FRSTOR(const X86::Instruction&) override;
+ virtual void FUCOM(const X86::Instruction&) override;
+ virtual void FUCOMP(const X86::Instruction&) override;
+ virtual void FNSAVE(const X86::Instruction&) override;
+ virtual void FNSTSW(const X86::Instruction&) override;
+ virtual void FIADD_RM16(const X86::Instruction&) override;
+ virtual void FADDP(const X86::Instruction&) override;
+ virtual void FIMUL_RM16(const X86::Instruction&) override;
+ virtual void FMULP(const X86::Instruction&) override;
+ virtual void FICOM_RM16(const X86::Instruction&) override;
+ virtual void FICOMP_RM16(const X86::Instruction&) override;
+ virtual void FCOMPP(const X86::Instruction&) override;
+ virtual void FISUB_RM16(const X86::Instruction&) override;
+ virtual void FSUBRP(const X86::Instruction&) override;
+ virtual void FISUBR_RM16(const X86::Instruction&) override;
+ virtual void FSUBP(const X86::Instruction&) override;
+ virtual void FIDIV_RM16(const X86::Instruction&) override;
+ virtual void FDIVRP(const X86::Instruction&) override;
+ virtual void FIDIVR_RM16(const X86::Instruction&) override;
+ virtual void FDIVP(const X86::Instruction&) override;
+ virtual void FILD_RM16(const X86::Instruction&) override;
+ virtual void FFREEP(const X86::Instruction&) override;
+ virtual void FISTTP_RM16(const X86::Instruction&) override;
+ virtual void FIST_RM16(const X86::Instruction&) override;
+ virtual void FISTP_RM16(const X86::Instruction&) override;
+ virtual void FBLD_M80(const X86::Instruction&) override;
+ virtual void FNSTSW_AX(const X86::Instruction&) override;
+ virtual void FILD_RM64(const X86::Instruction&) override;
+ virtual void FUCOMIP(const X86::Instruction&) override;
+ virtual void FBSTP_M80(const X86::Instruction&) override;
+ virtual void FCOMIP(const X86::Instruction&) override;
+ virtual void FISTP_RM64(const X86::Instruction&) override;
+ virtual void HLT(const X86::Instruction&) override;
+ virtual void IDIV_RM16(const X86::Instruction&) override;
+ virtual void IDIV_RM32(const X86::Instruction&) override;
+ virtual void IDIV_RM8(const X86::Instruction&) override;
+ virtual void IMUL_RM16(const X86::Instruction&) override;
+ virtual void IMUL_RM32(const X86::Instruction&) override;
+ virtual void IMUL_RM8(const X86::Instruction&) override;
+ virtual void IMUL_reg16_RM16(const X86::Instruction&) override;
+ virtual void IMUL_reg16_RM16_imm16(const X86::Instruction&) override;
+ virtual void IMUL_reg16_RM16_imm8(const X86::Instruction&) override;
+ virtual void IMUL_reg32_RM32(const X86::Instruction&) override;
+ virtual void IMUL_reg32_RM32_imm32(const X86::Instruction&) override;
+ virtual void IMUL_reg32_RM32_imm8(const X86::Instruction&) override;
+ virtual void INC_RM16(const X86::Instruction&) override;
+ virtual void INC_RM32(const X86::Instruction&) override;
+ virtual void INC_RM8(const X86::Instruction&) override;
+ virtual void INC_reg16(const X86::Instruction&) override;
+ virtual void INC_reg32(const X86::Instruction&) override;
+ virtual void INSB(const X86::Instruction&) override;
+ virtual void INSD(const X86::Instruction&) override;
+ virtual void INSW(const X86::Instruction&) override;
+ virtual void INT3(const X86::Instruction&) override;
+ virtual void INTO(const X86::Instruction&) override;
+ virtual void INT_imm8(const X86::Instruction&) override;
+ virtual void INVLPG(const X86::Instruction&) override;
+ virtual void IN_AL_DX(const X86::Instruction&) override;
+ virtual void IN_AL_imm8(const X86::Instruction&) override;
+ virtual void IN_AX_DX(const X86::Instruction&) override;
+ virtual void IN_AX_imm8(const X86::Instruction&) override;
+ virtual void IN_EAX_DX(const X86::Instruction&) override;
+ virtual void IN_EAX_imm8(const X86::Instruction&) override;
+ virtual void IRET(const X86::Instruction&) override;
+ virtual void JCXZ_imm8(const X86::Instruction&) override;
+ virtual void JMP_FAR_mem16(const X86::Instruction&) override;
+ virtual void JMP_FAR_mem32(const X86::Instruction&) override;
+ virtual void JMP_RM16(const X86::Instruction&) override;
+ virtual void JMP_RM32(const X86::Instruction&) override;
+ virtual void JMP_imm16(const X86::Instruction&) override;
+ virtual void JMP_imm16_imm16(const X86::Instruction&) override;
+ virtual void JMP_imm16_imm32(const X86::Instruction&) override;
+ virtual void JMP_imm32(const X86::Instruction&) override;
+ virtual void JMP_short_imm8(const X86::Instruction&) override;
+ virtual void Jcc_NEAR_imm(const X86::Instruction&) override;
+ virtual void Jcc_imm8(const X86::Instruction&) override;
+ virtual void LAHF(const X86::Instruction&) override;
+ virtual void LAR_reg16_RM16(const X86::Instruction&) override;
+ virtual void LAR_reg32_RM32(const X86::Instruction&) override;
+ virtual void LDS_reg16_mem16(const X86::Instruction&) override;
+ virtual void LDS_reg32_mem32(const X86::Instruction&) override;
+ virtual void LEAVE16(const X86::Instruction&) override;
+ virtual void LEAVE32(const X86::Instruction&) override;
+ virtual void LEA_reg16_mem16(const X86::Instruction&) override;
+ virtual void LEA_reg32_mem32(const X86::Instruction&) override;
+ virtual void LES_reg16_mem16(const X86::Instruction&) override;
+ virtual void LES_reg32_mem32(const X86::Instruction&) override;
+ virtual void LFS_reg16_mem16(const X86::Instruction&) override;
+ virtual void LFS_reg32_mem32(const X86::Instruction&) override;
+ virtual void LGDT(const X86::Instruction&) override;
+ virtual void LGS_reg16_mem16(const X86::Instruction&) override;
+ virtual void LGS_reg32_mem32(const X86::Instruction&) override;
+ virtual void LIDT(const X86::Instruction&) override;
+ virtual void LLDT_RM16(const X86::Instruction&) override;
+ virtual void LMSW_RM16(const X86::Instruction&) override;
+ virtual void LODSB(const X86::Instruction&) override;
+ virtual void LODSD(const X86::Instruction&) override;
+ virtual void LODSW(const X86::Instruction&) override;
+ virtual void LOOPNZ_imm8(const X86::Instruction&) override;
+ virtual void LOOPZ_imm8(const X86::Instruction&) override;
+ virtual void LOOP_imm8(const X86::Instruction&) override;
+ virtual void LSL_reg16_RM16(const X86::Instruction&) override;
+ virtual void LSL_reg32_RM32(const X86::Instruction&) override;
+ virtual void LSS_reg16_mem16(const X86::Instruction&) override;
+ virtual void LSS_reg32_mem32(const X86::Instruction&) override;
+ virtual void LTR_RM16(const X86::Instruction&) override;
+ virtual void MOVSB(const X86::Instruction&) override;
+ virtual void MOVSD(const X86::Instruction&) override;
+ virtual void MOVSW(const X86::Instruction&) override;
+ virtual void MOVSX_reg16_RM8(const X86::Instruction&) override;
+ virtual void MOVSX_reg32_RM16(const X86::Instruction&) override;
+ virtual void MOVSX_reg32_RM8(const X86::Instruction&) override;
+ virtual void MOVZX_reg16_RM8(const X86::Instruction&) override;
+ virtual void MOVZX_reg32_RM16(const X86::Instruction&) override;
+ virtual void MOVZX_reg32_RM8(const X86::Instruction&) override;
+ virtual void MOV_AL_moff8(const X86::Instruction&) override;
+ virtual void MOV_AX_moff16(const X86::Instruction&) override;
+ virtual void MOV_CR_reg32(const X86::Instruction&) override;
+ virtual void MOV_DR_reg32(const X86::Instruction&) override;
+ virtual void MOV_EAX_moff32(const X86::Instruction&) override;
+ virtual void MOV_RM16_imm16(const X86::Instruction&) override;
+ virtual void MOV_RM16_reg16(const X86::Instruction&) override;
+ virtual void MOV_RM16_seg(const X86::Instruction&) override;
+ virtual void MOV_RM32_imm32(const X86::Instruction&) override;
+ virtual void MOV_RM32_reg32(const X86::Instruction&) override;
+ virtual void MOV_RM8_imm8(const X86::Instruction&) override;
+ virtual void MOV_RM8_reg8(const X86::Instruction&) override;
+ virtual void MOV_moff16_AX(const X86::Instruction&) override;
+ virtual void MOV_moff32_EAX(const X86::Instruction&) override;
+ virtual void MOV_moff8_AL(const X86::Instruction&) override;
+ virtual void MOV_reg16_RM16(const X86::Instruction&) override;
+ virtual void MOV_reg16_imm16(const X86::Instruction&) override;
+ virtual void MOV_reg32_CR(const X86::Instruction&) override;
+ virtual void MOV_reg32_DR(const X86::Instruction&) override;
+ virtual void MOV_reg32_RM32(const X86::Instruction&) override;
+ virtual void MOV_reg32_imm32(const X86::Instruction&) override;
+ virtual void MOV_reg8_RM8(const X86::Instruction&) override;
+ virtual void MOV_reg8_imm8(const X86::Instruction&) override;
+ virtual void MOV_seg_RM16(const X86::Instruction&) override;
+ virtual void MOV_seg_RM32(const X86::Instruction&) override;
+ virtual void MUL_RM16(const X86::Instruction&) override;
+ virtual void MUL_RM32(const X86::Instruction&) override;
+ virtual void MUL_RM8(const X86::Instruction&) override;
+ virtual void NEG_RM16(const X86::Instruction&) override;
+ virtual void NEG_RM32(const X86::Instruction&) override;
+ virtual void NEG_RM8(const X86::Instruction&) override;
+ virtual void NOP(const X86::Instruction&) override;
+ virtual void NOT_RM16(const X86::Instruction&) override;
+ virtual void NOT_RM32(const X86::Instruction&) override;
+ virtual void NOT_RM8(const X86::Instruction&) override;
+ virtual void OR_AL_imm8(const X86::Instruction&) override;
+ virtual void OR_AX_imm16(const X86::Instruction&) override;
+ virtual void OR_EAX_imm32(const X86::Instruction&) override;
+ virtual void OR_RM16_imm16(const X86::Instruction&) override;
+ virtual void OR_RM16_imm8(const X86::Instruction&) override;
+ virtual void OR_RM16_reg16(const X86::Instruction&) override;
+ virtual void OR_RM32_imm32(const X86::Instruction&) override;
+ virtual void OR_RM32_imm8(const X86::Instruction&) override;
+ virtual void OR_RM32_reg32(const X86::Instruction&) override;
+ virtual void OR_RM8_imm8(const X86::Instruction&) override;
+ virtual void OR_RM8_reg8(const X86::Instruction&) override;
+ virtual void OR_reg16_RM16(const X86::Instruction&) override;
+ virtual void OR_reg32_RM32(const X86::Instruction&) override;
+ virtual void OR_reg8_RM8(const X86::Instruction&) override;
+ virtual void OUTSB(const X86::Instruction&) override;
+ virtual void OUTSD(const X86::Instruction&) override;
+ virtual void OUTSW(const X86::Instruction&) override;
+ virtual void OUT_DX_AL(const X86::Instruction&) override;
+ virtual void OUT_DX_AX(const X86::Instruction&) override;
+ virtual void OUT_DX_EAX(const X86::Instruction&) override;
+ virtual void OUT_imm8_AL(const X86::Instruction&) override;
+ virtual void OUT_imm8_AX(const X86::Instruction&) override;
+ virtual void OUT_imm8_EAX(const X86::Instruction&) override;
+ virtual void PADDB_mm1_mm2m64(const X86::Instruction&) override;
+ virtual void PADDW_mm1_mm2m64(const X86::Instruction&) override;
+ virtual void PADDD_mm1_mm2m64(const X86::Instruction&) override;
+ virtual void POPA(const X86::Instruction&) override;
+ virtual void POPAD(const X86::Instruction&) override;
+ virtual void POPF(const X86::Instruction&) override;
+ virtual void POPFD(const X86::Instruction&) override;
+ virtual void POP_DS(const X86::Instruction&) override;
+ virtual void POP_ES(const X86::Instruction&) override;
+ virtual void POP_FS(const X86::Instruction&) override;
+ virtual void POP_GS(const X86::Instruction&) override;
+ virtual void POP_RM16(const X86::Instruction&) override;
+ virtual void POP_RM32(const X86::Instruction&) override;
+ virtual void POP_SS(const X86::Instruction&) override;
+ virtual void POP_reg16(const X86::Instruction&) override;
+ virtual void POP_reg32(const X86::Instruction&) override;
+ virtual void PUSHA(const X86::Instruction&) override;
+ virtual void PUSHAD(const X86::Instruction&) override;
+ virtual void PUSHF(const X86::Instruction&) override;
+ virtual void PUSHFD(const X86::Instruction&) override;
+ virtual void PUSH_CS(const X86::Instruction&) override;
+ virtual void PUSH_DS(const X86::Instruction&) override;
+ virtual void PUSH_ES(const X86::Instruction&) override;
+ virtual void PUSH_FS(const X86::Instruction&) override;
+ virtual void PUSH_GS(const X86::Instruction&) override;
+ virtual void PUSH_RM16(const X86::Instruction&) override;
+ virtual void PUSH_RM32(const X86::Instruction&) override;
+ virtual void PUSH_SP_8086_80186(const X86::Instruction&) override;
+ virtual void PUSH_SS(const X86::Instruction&) override;
+ virtual void PUSH_imm16(const X86::Instruction&) override;
+ virtual void PUSH_imm32(const X86::Instruction&) override;
+ virtual void PUSH_imm8(const X86::Instruction&) override;
+ virtual void PUSH_reg16(const X86::Instruction&) override;
+ virtual void PUSH_reg32(const X86::Instruction&) override;
+ virtual void RCL_RM16_1(const X86::Instruction&) override;
+ virtual void RCL_RM16_CL(const X86::Instruction&) override;
+ virtual void RCL_RM16_imm8(const X86::Instruction&) override;
+ virtual void RCL_RM32_1(const X86::Instruction&) override;
+ virtual void RCL_RM32_CL(const X86::Instruction&) override;
+ virtual void RCL_RM32_imm8(const X86::Instruction&) override;
+ virtual void RCL_RM8_1(const X86::Instruction&) override;
+ virtual void RCL_RM8_CL(const X86::Instruction&) override;
+ virtual void RCL_RM8_imm8(const X86::Instruction&) override;
+ virtual void RCR_RM16_1(const X86::Instruction&) override;
+ virtual void RCR_RM16_CL(const X86::Instruction&) override;
+ virtual void RCR_RM16_imm8(const X86::Instruction&) override;
+ virtual void RCR_RM32_1(const X86::Instruction&) override;
+ virtual void RCR_RM32_CL(const X86::Instruction&) override;
+ virtual void RCR_RM32_imm8(const X86::Instruction&) override;
+ virtual void RCR_RM8_1(const X86::Instruction&) override;
+ virtual void RCR_RM8_CL(const X86::Instruction&) override;
+ virtual void RCR_RM8_imm8(const X86::Instruction&) override;
+ virtual void RDTSC(const X86::Instruction&) override;
+ virtual void RET(const X86::Instruction&) override;
+ virtual void RETF(const X86::Instruction&) override;
+ virtual void RETF_imm16(const X86::Instruction&) override;
+ virtual void RET_imm16(const X86::Instruction&) override;
+ virtual void ROL_RM16_1(const X86::Instruction&) override;
+ virtual void ROL_RM16_CL(const X86::Instruction&) override;
+ virtual void ROL_RM16_imm8(const X86::Instruction&) override;
+ virtual void ROL_RM32_1(const X86::Instruction&) override;
+ virtual void ROL_RM32_CL(const X86::Instruction&) override;
+ virtual void ROL_RM32_imm8(const X86::Instruction&) override;
+ virtual void ROL_RM8_1(const X86::Instruction&) override;
+ virtual void ROL_RM8_CL(const X86::Instruction&) override;
+ virtual void ROL_RM8_imm8(const X86::Instruction&) override;
+ virtual void ROR_RM16_1(const X86::Instruction&) override;
+ virtual void ROR_RM16_CL(const X86::Instruction&) override;
+ virtual void ROR_RM16_imm8(const X86::Instruction&) override;
+ virtual void ROR_RM32_1(const X86::Instruction&) override;
+ virtual void ROR_RM32_CL(const X86::Instruction&) override;
+ virtual void ROR_RM32_imm8(const X86::Instruction&) override;
+ virtual void ROR_RM8_1(const X86::Instruction&) override;
+ virtual void ROR_RM8_CL(const X86::Instruction&) override;
+ virtual void ROR_RM8_imm8(const X86::Instruction&) override;
+ virtual void SAHF(const X86::Instruction&) override;
+ virtual void SALC(const X86::Instruction&) override;
+ virtual void SAR_RM16_1(const X86::Instruction&) override;
+ virtual void SAR_RM16_CL(const X86::Instruction&) override;
+ virtual void SAR_RM16_imm8(const X86::Instruction&) override;
+ virtual void SAR_RM32_1(const X86::Instruction&) override;
+ virtual void SAR_RM32_CL(const X86::Instruction&) override;
+ virtual void SAR_RM32_imm8(const X86::Instruction&) override;
+ virtual void SAR_RM8_1(const X86::Instruction&) override;
+ virtual void SAR_RM8_CL(const X86::Instruction&) override;
+ virtual void SAR_RM8_imm8(const X86::Instruction&) override;
+ virtual void SBB_AL_imm8(const X86::Instruction&) override;
+ virtual void SBB_AX_imm16(const X86::Instruction&) override;
+ virtual void SBB_EAX_imm32(const X86::Instruction&) override;
+ virtual void SBB_RM16_imm16(const X86::Instruction&) override;
+ virtual void SBB_RM16_imm8(const X86::Instruction&) override;
+ virtual void SBB_RM16_reg16(const X86::Instruction&) override;
+ virtual void SBB_RM32_imm32(const X86::Instruction&) override;
+ virtual void SBB_RM32_imm8(const X86::Instruction&) override;
+ virtual void SBB_RM32_reg32(const X86::Instruction&) override;
+ virtual void SBB_RM8_imm8(const X86::Instruction&) override;
+ virtual void SBB_RM8_reg8(const X86::Instruction&) override;
+ virtual void SBB_reg16_RM16(const X86::Instruction&) override;
+ virtual void SBB_reg32_RM32(const X86::Instruction&) override;
+ virtual void SBB_reg8_RM8(const X86::Instruction&) override;
+ virtual void SCASB(const X86::Instruction&) override;
+ virtual void SCASD(const X86::Instruction&) override;
+ virtual void SCASW(const X86::Instruction&) override;
+ virtual void SETcc_RM8(const X86::Instruction&) override;
+ virtual void SGDT(const X86::Instruction&) override;
+ virtual void SHLD_RM16_reg16_CL(const X86::Instruction&) override;
+ virtual void SHLD_RM16_reg16_imm8(const X86::Instruction&) override;
+ virtual void SHLD_RM32_reg32_CL(const X86::Instruction&) override;
+ virtual void SHLD_RM32_reg32_imm8(const X86::Instruction&) override;
+ virtual void SHL_RM16_1(const X86::Instruction&) override;
+ virtual void SHL_RM16_CL(const X86::Instruction&) override;
+ virtual void SHL_RM16_imm8(const X86::Instruction&) override;
+ virtual void SHL_RM32_1(const X86::Instruction&) override;
+ virtual void SHL_RM32_CL(const X86::Instruction&) override;
+ virtual void SHL_RM32_imm8(const X86::Instruction&) override;
+ virtual void SHL_RM8_1(const X86::Instruction&) override;
+ virtual void SHL_RM8_CL(const X86::Instruction&) override;
+ virtual void SHL_RM8_imm8(const X86::Instruction&) override;
+ virtual void SHRD_RM16_reg16_CL(const X86::Instruction&) override;
+ virtual void SHRD_RM16_reg16_imm8(const X86::Instruction&) override;
+ virtual void SHRD_RM32_reg32_CL(const X86::Instruction&) override;
+ virtual void SHRD_RM32_reg32_imm8(const X86::Instruction&) override;
+ virtual void SHR_RM16_1(const X86::Instruction&) override;
+ virtual void SHR_RM16_CL(const X86::Instruction&) override;
+ virtual void SHR_RM16_imm8(const X86::Instruction&) override;
+ virtual void SHR_RM32_1(const X86::Instruction&) override;
+ virtual void SHR_RM32_CL(const X86::Instruction&) override;
+ virtual void SHR_RM32_imm8(const X86::Instruction&) override;
+ virtual void SHR_RM8_1(const X86::Instruction&) override;
+ virtual void SHR_RM8_CL(const X86::Instruction&) override;
+ virtual void SHR_RM8_imm8(const X86::Instruction&) override;
+ virtual void SIDT(const X86::Instruction&) override;
+ virtual void SLDT_RM16(const X86::Instruction&) override;
+ virtual void SMSW_RM16(const X86::Instruction&) override;
+ virtual void STC(const X86::Instruction&) override;
+ virtual void STD(const X86::Instruction&) override;
+ virtual void STI(const X86::Instruction&) override;
+ virtual void STOSB(const X86::Instruction&) override;
+ virtual void STOSD(const X86::Instruction&) override;
+ virtual void STOSW(const X86::Instruction&) override;
+ virtual void STR_RM16(const X86::Instruction&) override;
+ virtual void SUB_AL_imm8(const X86::Instruction&) override;
+ virtual void SUB_AX_imm16(const X86::Instruction&) override;
+ virtual void SUB_EAX_imm32(const X86::Instruction&) override;
+ virtual void SUB_RM16_imm16(const X86::Instruction&) override;
+ virtual void SUB_RM16_imm8(const X86::Instruction&) override;
+ virtual void SUB_RM16_reg16(const X86::Instruction&) override;
+ virtual void SUB_RM32_imm32(const X86::Instruction&) override;
+ virtual void SUB_RM32_imm8(const X86::Instruction&) override;
+ virtual void SUB_RM32_reg32(const X86::Instruction&) override;
+ virtual void SUB_RM8_imm8(const X86::Instruction&) override;
+ virtual void SUB_RM8_reg8(const X86::Instruction&) override;
+ virtual void SUB_reg16_RM16(const X86::Instruction&) override;
+ virtual void SUB_reg32_RM32(const X86::Instruction&) override;
+ virtual void SUB_reg8_RM8(const X86::Instruction&) override;
+ virtual void TEST_AL_imm8(const X86::Instruction&) override;
+ virtual void TEST_AX_imm16(const X86::Instruction&) override;
+ virtual void TEST_EAX_imm32(const X86::Instruction&) override;
+ virtual void TEST_RM16_imm16(const X86::Instruction&) override;
+ virtual void TEST_RM16_reg16(const X86::Instruction&) override;
+ virtual void TEST_RM32_imm32(const X86::Instruction&) override;
+ virtual void TEST_RM32_reg32(const X86::Instruction&) override;
+ virtual void TEST_RM8_imm8(const X86::Instruction&) override;
+ virtual void TEST_RM8_reg8(const X86::Instruction&) override;
+ virtual void UD0(const X86::Instruction&) override;
+ virtual void UD1(const X86::Instruction&) override;
+ virtual void UD2(const X86::Instruction&) override;
+ virtual void VERR_RM16(const X86::Instruction&) override;
+ virtual void VERW_RM16(const X86::Instruction&) override;
+ virtual void WAIT(const X86::Instruction&) override;
+ virtual void WBINVD(const X86::Instruction&) override;
+ virtual void XADD_RM16_reg16(const X86::Instruction&) override;
+ virtual void XADD_RM32_reg32(const X86::Instruction&) override;
+ virtual void XADD_RM8_reg8(const X86::Instruction&) override;
+ virtual void XCHG_AX_reg16(const X86::Instruction&) override;
+ virtual void XCHG_EAX_reg32(const X86::Instruction&) override;
+ virtual void XCHG_reg16_RM16(const X86::Instruction&) override;
+ virtual void XCHG_reg32_RM32(const X86::Instruction&) override;
+ virtual void XCHG_reg8_RM8(const X86::Instruction&) override;
+ virtual void XLAT(const X86::Instruction&) override;
+ virtual void XOR_AL_imm8(const X86::Instruction&) override;
+ virtual void XOR_AX_imm16(const X86::Instruction&) override;
+ virtual void XOR_EAX_imm32(const X86::Instruction&) override;
+ virtual void XOR_RM16_imm16(const X86::Instruction&) override;
+ virtual void XOR_RM16_imm8(const X86::Instruction&) override;
+ virtual void XOR_RM16_reg16(const X86::Instruction&) override;
+ virtual void XOR_RM32_imm32(const X86::Instruction&) override;
+ virtual void XOR_RM32_imm8(const X86::Instruction&) override;
+ virtual void XOR_RM32_reg32(const X86::Instruction&) override;
+ virtual void XOR_RM8_imm8(const X86::Instruction&) override;
+ virtual void XOR_RM8_reg8(const X86::Instruction&) override;
+ virtual void XOR_reg16_RM16(const X86::Instruction&) override;
+ virtual void XOR_reg32_RM32(const X86::Instruction&) override;
+ virtual void XOR_reg8_RM8(const X86::Instruction&) override;
+ virtual void MOVQ_mm1_mm2m64(const X86::Instruction&) override;
+ virtual void EMMS(const X86::Instruction&) override;
+ virtual void MOVQ_mm1_m64_mm2(const X86::Instruction&) override;
+ virtual void wrap_0xC0(const X86::Instruction&) override;
+ virtual void wrap_0xC1_16(const X86::Instruction&) override;
+ virtual void wrap_0xC1_32(const X86::Instruction&) override;
+ virtual void wrap_0xD0(const X86::Instruction&) override;
+ virtual void wrap_0xD1_16(const X86::Instruction&) override;
+ virtual void wrap_0xD1_32(const X86::Instruction&) override;
+ virtual void wrap_0xD2(const X86::Instruction&) override;
+ virtual void wrap_0xD3_16(const X86::Instruction&) override;
+ virtual void wrap_0xD3_32(const X86::Instruction&) override;
+
+ template<bool update_dest, bool is_or, typename Op>
+ void generic_AL_imm8(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_or, typename Op>
+ void generic_AX_imm16(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_or, typename Op>
+ void generic_EAX_imm32(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_or, typename Op>
+ void generic_RM16_imm16(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_or, typename Op>
+ void generic_RM16_imm8(Op, const X86::Instruction&);
+ template<bool update_dest, typename Op>
+ void generic_RM16_unsigned_imm8(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_zero_idiom_if_both_operands_same, typename Op>
+ void generic_RM16_reg16(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_or, typename Op>
+ void generic_RM32_imm32(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_or, typename Op>
+ void generic_RM32_imm8(Op, const X86::Instruction&);
+ template<bool update_dest, typename Op>
+ void generic_RM32_unsigned_imm8(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_zero_idiom_if_both_operands_same, typename Op>
+ void generic_RM32_reg32(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_or, typename Op>
+ void generic_RM8_imm8(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_zero_idiom_if_both_operands_same, typename Op>
+ void generic_RM8_reg8(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_zero_idiom_if_both_operands_same, typename Op>
+ void generic_reg16_RM16(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_zero_idiom_if_both_operands_same, typename Op>
+ void generic_reg32_RM32(Op, const X86::Instruction&);
+ template<bool update_dest, bool is_zero_idiom_if_both_operands_same, typename Op>
+ void generic_reg8_RM8(Op, const X86::Instruction&);
+
+ template<typename Op>
+ void generic_RM8_1(Op, const X86::Instruction&);
+ template<typename Op>
+ void generic_RM8_CL(Op, const X86::Instruction&);
+ template<typename Op>
+ void generic_RM16_1(Op, const X86::Instruction&);
+ template<typename Op>
+ void generic_RM16_CL(Op, const X86::Instruction&);
+ template<typename Op>
+ void generic_RM32_1(Op, const X86::Instruction&);
+ template<typename Op>
+ void generic_RM32_CL(Op, const X86::Instruction&);
+
+ void update_code_cache();
+
+ void did_receive_secret_data();
+
+private:
+ Emulator& m_emulator;
+
+ PartAddressableRegister m_gpr[8];
+ PartAddressableRegister m_gpr_shadow[8];
+
+ u16 m_segment[8] { 0 };
+ u32 m_eflags { 0 };
+
+ bool m_flags_tainted { false };
+
+ u32 m_eip { 0 };
+ u32 m_base_eip { 0 };
+
+ long double m_fpu[8];
+ // FIXME: Shadow for m_fpu.
+
+ // FIXME: Use bits 11 to 13 in the FPU status word for this.
+ int m_fpu_top { -1 };
+
+ void fpu_push(long double n)
+ {
+ ++m_fpu_top;
+ fpu_set(0, n);
+ }
+ long double fpu_pop()
+ {
+ auto n = fpu_get(0);
+ m_fpu_top--;
+ return n;
+ }
+ long double fpu_get(int i)
+ {
+ ASSERT(i >= 0 && i <= m_fpu_top);
+ return m_fpu[m_fpu_top - i];
+ }
+ void fpu_set(int i, long double n)
+ {
+ ASSERT(i >= 0 && i <= m_fpu_top);
+ m_fpu[m_fpu_top - i] = n;
+ }
+
+ // FIXME: Or just something like m_flags_tainted?
+ ValueWithShadow<u16> m_fpu_cw { 0, 0 };
+
+ Region* m_cached_code_region { nullptr };
+ u8* m_cached_code_base_ptr { nullptr };
+
+ u32 m_secret_handshake_state { 0 };
+ u32 m_secret_data[3];
+};
+
+ALWAYS_INLINE u8 SoftCPU::read8()
+{
+ if (!m_cached_code_region || !m_cached_code_region->contains(m_eip))
+ update_code_cache();
+
+ u8 value = m_cached_code_base_ptr[m_eip - m_cached_code_region->base()];
+ m_eip += 1;
+ return value;
+}
+
+ALWAYS_INLINE u16 SoftCPU::read16()
+{
+ if (!m_cached_code_region || !m_cached_code_region->contains(m_eip))
+ update_code_cache();
+
+ u16 value = *reinterpret_cast<const u16*>(&m_cached_code_base_ptr[m_eip - m_cached_code_region->base()]);
+ m_eip += 2;
+ return value;
+}
+
+ALWAYS_INLINE u32 SoftCPU::read32()
+{
+ if (!m_cached_code_region || !m_cached_code_region->contains(m_eip))
+ update_code_cache();
+
+ u32 value = *reinterpret_cast<const u32*>(&m_cached_code_base_ptr[m_eip - m_cached_code_region->base()]);
+ m_eip += 4;
+ return value;
+}
+
+ALWAYS_INLINE u64 SoftCPU::read64()
+{
+ if (!m_cached_code_region || !m_cached_code_region->contains(m_eip))
+ update_code_cache();
+
+ auto value = *reinterpret_cast<const u64*>(&m_cached_code_base_ptr[m_eip - m_cached_code_region->base()]);
+ m_eip += 8;
+ return value;
+}
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/SoftMMU.cpp b/Userland/DevTools/UserspaceEmulator/SoftMMU.cpp
new file mode 100644
index 0000000000..50da131f1c
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/SoftMMU.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "SoftMMU.h"
+#include "Emulator.h"
+#include "MmapRegion.h"
+#include "Report.h"
+#include "SharedBufferRegion.h"
+#include <AK/ByteBuffer.h>
+#include <AK/Memory.h>
+
+namespace UserspaceEmulator {
+
+SoftMMU::SoftMMU(Emulator& emulator)
+ : m_emulator(emulator)
+{
+}
+
+void SoftMMU::add_region(NonnullOwnPtr<Region> region)
+{
+ ASSERT(!find_region({ 0x23, region->base() }));
+
+ // FIXME: More sanity checks pls
+ if (is<SharedBufferRegion>(*region))
+ m_shbuf_regions.set(static_cast<SharedBufferRegion*>(region.ptr())->shbuf_id(), region.ptr());
+
+ size_t first_page_in_region = region->base() / PAGE_SIZE;
+ size_t last_page_in_region = (region->base() + region->size() - 1) / PAGE_SIZE;
+ for (size_t page = first_page_in_region; page <= last_page_in_region; ++page) {
+ m_page_to_region_map[page] = region.ptr();
+ }
+
+ m_regions.append(move(region));
+}
+
+void SoftMMU::remove_region(Region& region)
+{
+ size_t first_page_in_region = region.base() / PAGE_SIZE;
+ for (size_t i = 0; i < ceil_div(region.size(), PAGE_SIZE); ++i) {
+ m_page_to_region_map[first_page_in_region + i] = nullptr;
+ }
+
+ if (is<SharedBufferRegion>(region))
+ m_shbuf_regions.remove(static_cast<SharedBufferRegion&>(region).shbuf_id());
+ m_regions.remove_first_matching([&](auto& entry) { return entry.ptr() == &region; });
+}
+
+void SoftMMU::set_tls_region(NonnullOwnPtr<Region> region)
+{
+ ASSERT(!m_tls_region);
+ m_tls_region = move(region);
+}
+
+ValueWithShadow<u8> SoftMMU::read8(X86::LogicalAddress address)
+{
+ auto* region = find_region(address);
+ if (!region) {
+ reportln("SoftMMU::read8: No region for @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ if (!region->is_readable()) {
+ reportln("SoftMMU::read8: Non-readable region @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ return region->read8(address.offset() - region->base());
+}
+
+ValueWithShadow<u16> SoftMMU::read16(X86::LogicalAddress address)
+{
+ auto* region = find_region(address);
+ if (!region) {
+ reportln("SoftMMU::read16: No region for @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ if (!region->is_readable()) {
+ reportln("SoftMMU::read16: Non-readable region @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ return region->read16(address.offset() - region->base());
+}
+
+ValueWithShadow<u32> SoftMMU::read32(X86::LogicalAddress address)
+{
+ auto* region = find_region(address);
+ if (!region) {
+ reportln("SoftMMU::read32: No region for @ {:04x}:{:p}", address.selector(), address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ if (!region->is_readable()) {
+ reportln("SoftMMU::read32: Non-readable region @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ return region->read32(address.offset() - region->base());
+}
+
+ValueWithShadow<u64> SoftMMU::read64(X86::LogicalAddress address)
+{
+ auto* region = find_region(address);
+ if (!region) {
+ reportln("SoftMMU::read64: No region for @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ if (!region->is_readable()) {
+ reportln("SoftMMU::read64: Non-readable region @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ return region->read64(address.offset() - region->base());
+}
+
+void SoftMMU::write8(X86::LogicalAddress address, ValueWithShadow<u8> value)
+{
+ auto* region = find_region(address);
+ if (!region) {
+ reportln("SoftMMU::write8: No region for @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ if (!region->is_writable()) {
+ reportln("SoftMMU::write8: Non-writable region @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+ region->write8(address.offset() - region->base(), value);
+}
+
+void SoftMMU::write16(X86::LogicalAddress address, ValueWithShadow<u16> value)
+{
+ auto* region = find_region(address);
+ if (!region) {
+ reportln("SoftMMU::write16: No region for @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ if (!region->is_writable()) {
+ reportln("SoftMMU::write16: Non-writable region @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ region->write16(address.offset() - region->base(), value);
+}
+
+void SoftMMU::write32(X86::LogicalAddress address, ValueWithShadow<u32> value)
+{
+ auto* region = find_region(address);
+ if (!region) {
+ reportln("SoftMMU::write32: No region for @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ if (!region->is_writable()) {
+ reportln("SoftMMU::write32: Non-writable region @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ region->write32(address.offset() - region->base(), value);
+}
+
+void SoftMMU::write64(X86::LogicalAddress address, ValueWithShadow<u64> value)
+{
+ auto* region = find_region(address);
+ if (!region) {
+ reportln("SoftMMU::write64: No region for @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ if (!region->is_writable()) {
+ reportln("SoftMMU::write64: Non-writable region @ {:p}", address.offset());
+ m_emulator.dump_backtrace();
+ TODO();
+ }
+
+ region->write64(address.offset() - region->base(), value);
+}
+
+void SoftMMU::copy_to_vm(FlatPtr destination, const void* source, size_t size)
+{
+ // FIXME: We should have a way to preserve the shadow data here as well.
+ for (size_t i = 0; i < size; ++i)
+ write8({ 0x23, destination + i }, shadow_wrap_as_initialized(((const u8*)source)[i]));
+}
+
+void SoftMMU::copy_from_vm(void* destination, const FlatPtr source, size_t size)
+{
+ // FIXME: We should have a way to preserve the shadow data here as well.
+ for (size_t i = 0; i < size; ++i)
+ ((u8*)destination)[i] = read8({ 0x23, source + i }).value();
+}
+
+ByteBuffer SoftMMU::copy_buffer_from_vm(const FlatPtr source, size_t size)
+{
+ auto buffer = ByteBuffer::create_uninitialized(size);
+ copy_from_vm(buffer.data(), source, size);
+ return buffer;
+}
+
+SharedBufferRegion* SoftMMU::shbuf_region(int shbuf_id)
+{
+ return (SharedBufferRegion*)m_shbuf_regions.get(shbuf_id).value_or(nullptr);
+}
+
+bool SoftMMU::fast_fill_memory8(X86::LogicalAddress address, size_t size, ValueWithShadow<u8> value)
+{
+ if (!size)
+ return true;
+ auto* region = find_region(address);
+ if (!region)
+ return false;
+ if (!region->contains(address.offset() + size - 1))
+ return false;
+
+ if (is<MmapRegion>(*region) && static_cast<const MmapRegion&>(*region).is_malloc_block()) {
+ if (auto* tracer = m_emulator.malloc_tracer()) {
+ // FIXME: Add a way to audit an entire range of memory instead of looping here!
+ for (size_t i = 0; i < size; ++i) {
+ tracer->audit_write(*region, address.offset() + (i * sizeof(u8)), sizeof(u8));
+ }
+ }
+ }
+
+ size_t offset_in_region = address.offset() - region->base();
+ memset(region->data() + offset_in_region, value.value(), size);
+ memset(region->shadow_data() + offset_in_region, value.shadow(), size);
+ return true;
+}
+
+bool SoftMMU::fast_fill_memory32(X86::LogicalAddress address, size_t count, ValueWithShadow<u32> value)
+{
+ if (!count)
+ return true;
+ auto* region = find_region(address);
+ if (!region)
+ return false;
+ if (!region->contains(address.offset() + (count * sizeof(u32)) - 1))
+ return false;
+
+ if (is<MmapRegion>(*region) && static_cast<const MmapRegion&>(*region).is_malloc_block()) {
+ if (auto* tracer = m_emulator.malloc_tracer()) {
+ // FIXME: Add a way to audit an entire range of memory instead of looping here!
+ for (size_t i = 0; i < count; ++i) {
+ tracer->audit_write(*region, address.offset() + (i * sizeof(u32)), sizeof(u32));
+ }
+ }
+ }
+
+ size_t offset_in_region = address.offset() - region->base();
+ fast_u32_fill((u32*)(region->data() + offset_in_region), value.value(), count);
+ fast_u32_fill((u32*)(region->shadow_data() + offset_in_region), value.shadow(), count);
+ return true;
+}
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/SoftMMU.h b/Userland/DevTools/UserspaceEmulator/SoftMMU.h
new file mode 100644
index 0000000000..33351ff57b
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/SoftMMU.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Region.h"
+#include "ValueWithShadow.h"
+#include <AK/HashMap.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <AK/OwnPtr.h>
+#include <AK/Types.h>
+#include <LibX86/Instruction.h>
+
+namespace UserspaceEmulator {
+
+class Emulator;
+class SharedBufferRegion;
+
+class SoftMMU {
+public:
+ explicit SoftMMU(Emulator&);
+
+ ValueWithShadow<u8> read8(X86::LogicalAddress);
+ ValueWithShadow<u16> read16(X86::LogicalAddress);
+ ValueWithShadow<u32> read32(X86::LogicalAddress);
+ ValueWithShadow<u64> read64(X86::LogicalAddress);
+
+ void write8(X86::LogicalAddress, ValueWithShadow<u8>);
+ void write16(X86::LogicalAddress, ValueWithShadow<u16>);
+ void write32(X86::LogicalAddress, ValueWithShadow<u32>);
+ void write64(X86::LogicalAddress, ValueWithShadow<u64>);
+
+ ALWAYS_INLINE Region* find_region(X86::LogicalAddress address)
+ {
+ if (address.selector() == 0x2b)
+ return m_tls_region.ptr();
+
+ size_t page_index = (address.offset() & ~(PAGE_SIZE - 1)) / PAGE_SIZE;
+ return m_page_to_region_map[page_index];
+ }
+
+ void add_region(NonnullOwnPtr<Region>);
+ void remove_region(Region&);
+
+ void set_tls_region(NonnullOwnPtr<Region>);
+
+ bool fast_fill_memory8(X86::LogicalAddress, size_t size, ValueWithShadow<u8>);
+ bool fast_fill_memory32(X86::LogicalAddress, size_t size, ValueWithShadow<u32>);
+
+ void copy_to_vm(FlatPtr destination, const void* source, size_t);
+ void copy_from_vm(void* destination, const FlatPtr source, size_t);
+ ByteBuffer copy_buffer_from_vm(const FlatPtr source, size_t);
+
+ SharedBufferRegion* shbuf_region(int shbuf_id);
+
+ template<typename Callback>
+ void for_each_region(Callback callback)
+ {
+ if (m_tls_region) {
+ if (callback(*m_tls_region) == IterationDecision::Break)
+ return;
+ }
+ for (auto& region : m_regions) {
+ if (callback(region) == IterationDecision::Break)
+ return;
+ }
+ }
+
+private:
+ Emulator& m_emulator;
+
+ Region* m_page_to_region_map[786432];
+
+ OwnPtr<Region> m_tls_region;
+ NonnullOwnPtrVector<Region> m_regions;
+ HashMap<int, Region*> m_shbuf_regions;
+};
+
+}
diff --git a/Userland/DevTools/UserspaceEmulator/ValueWithShadow.h b/Userland/DevTools/UserspaceEmulator/ValueWithShadow.h
new file mode 100644
index 0000000000..3684482642
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/ValueWithShadow.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <AK/Format.h>
+#include <AK/Platform.h>
+
+#pragma once
+
+namespace UserspaceEmulator {
+
+template<typename T>
+class ValueAndShadowReference;
+
+template<typename T>
+class ValueWithShadow {
+public:
+ using ValueType = T;
+
+ ValueWithShadow(T value, T shadow)
+ : m_value(value)
+ , m_shadow(shadow)
+ {
+ }
+
+ ValueWithShadow(const ValueAndShadowReference<T>&);
+
+ T value() const { return m_value; }
+ T shadow() const { return m_shadow; }
+
+ bool is_uninitialized() const
+ {
+ if constexpr (sizeof(T) == 4)
+ return (m_shadow & 0x01010101) != 0x01010101;
+ if constexpr (sizeof(T) == 2)
+ return (m_shadow & 0x0101) != 0x0101;
+ if constexpr (sizeof(T) == 1)
+ return (m_shadow & 0x01) != 0x01;
+ }
+
+ void set_initialized()
+ {
+ if constexpr (sizeof(T) == 4)
+ m_shadow = 0x01010101;
+ if constexpr (sizeof(T) == 2)
+ m_shadow = 0x0101;
+ if constexpr (sizeof(T) == 1)
+ m_shadow = 0x01;
+ }
+
+private:
+ T m_value;
+ T m_shadow;
+};
+
+template<typename T>
+class ValueAndShadowReference {
+public:
+ using ValueType = T;
+
+ ValueAndShadowReference(T& value, T& shadow)
+ : m_value(value)
+ , m_shadow(shadow)
+ {
+ }
+
+ bool is_uninitialized() const
+ {
+ if constexpr (sizeof(T) == 4)
+ return (m_shadow & 0x01010101) != 0x01010101;
+ if constexpr (sizeof(T) == 2)
+ return (m_shadow & 0x0101) != 0x0101;
+ if constexpr (sizeof(T) == 1)
+ return (m_shadow & 0x01) != 0x01;
+ }
+
+ void operator=(const ValueWithShadow<T>&);
+
+ T& value() { return m_value; }
+ T& shadow() { return m_shadow; }
+
+ const T& value() const { return m_value; }
+ const T& shadow() const { return m_shadow; }
+
+private:
+ T& m_value;
+ T& m_shadow;
+};
+
+template<typename T>
+ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_as_initialized(T value)
+{
+ if constexpr (sizeof(T) == 8)
+ return { value, 0x01010101'01010101LLU };
+ if constexpr (sizeof(T) == 4)
+ return { value, 0x01010101 };
+ if constexpr (sizeof(T) == 2)
+ return { value, 0x0101 };
+ if constexpr (sizeof(T) == 1)
+ return { value, 0x01 };
+}
+
+template<typename T, typename U>
+ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_with_taint_from(T value, const U& taint_a)
+{
+ if (taint_a.is_uninitialized())
+ return { value, 0 };
+ return shadow_wrap_as_initialized(value);
+}
+
+template<typename T, typename U, typename V>
+ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_with_taint_from(T value, const U& taint_a, const V& taint_b)
+{
+ if (taint_a.is_uninitialized() || taint_b.is_uninitialized())
+ return { value, 0 };
+ return shadow_wrap_as_initialized(value);
+}
+
+template<typename T, typename U, typename V, typename X>
+ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_with_taint_from(T value, const U& taint_a, const V& taint_b, const X& taint_c)
+{
+ if (taint_a.is_uninitialized() || taint_b.is_uninitialized() || taint_c.is_uninitialized())
+ return { value, 0 };
+ return shadow_wrap_as_initialized(value);
+}
+
+template<typename T>
+inline ValueWithShadow<T>::ValueWithShadow(const ValueAndShadowReference<T>& other)
+ : m_value(other.value())
+ , m_shadow(other.shadow())
+{
+}
+
+template<typename T>
+inline void ValueAndShadowReference<T>::operator=(const ValueWithShadow<T>& other)
+{
+ m_value = other.value();
+ m_shadow = other.shadow();
+}
+
+}
+
+template<typename T>
+struct AK::Formatter<UserspaceEmulator::ValueWithShadow<T>> : AK::Formatter<T> {
+ void format(FormatBuilder& builder, UserspaceEmulator::ValueWithShadow<T> value)
+ {
+ return Formatter<T>::format(builder, value.value());
+ }
+};
diff --git a/Userland/DevTools/UserspaceEmulator/main.cpp b/Userland/DevTools/UserspaceEmulator/main.cpp
new file mode 100644
index 0000000000..432c9a4a75
--- /dev/null
+++ b/Userland/DevTools/UserspaceEmulator/main.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Emulator.h"
+#include "SoftCPU.h"
+#include <AK/Format.h>
+#include <AK/LexicalPath.h>
+#include <AK/MappedFile.h>
+#include <AK/StringBuilder.h>
+#include <LibCore/ArgsParser.h>
+#include <LibCore/DirIterator.h>
+#include <LibELF/Image.h>
+#include <getopt.h>
+#include <pthread.h>
+#include <string.h>
+
+bool g_report_to_debug = false;
+
+int main(int argc, char** argv, char** env)
+{
+ Vector<const char*> command;
+
+ Core::ArgsParser parser;
+ parser.add_option(g_report_to_debug, "Write reports to the debug log", "report-to-debug", 0);
+ parser.add_positional_argument(command, "Command to emulate", "command");
+ parser.parse(argc, argv);
+
+ auto executable_path = Core::find_executable_in_path(command[0]);
+
+ Vector<String> arguments;
+ for (auto arg : command) {
+ arguments.append(arg);
+ }
+
+ Vector<String> environment;
+ for (int i = 0; env[i]; ++i) {
+ environment.append(env[i]);
+ }
+
+ // FIXME: It might be nice to tear down the emulator properly.
+ auto& emulator = *new UserspaceEmulator::Emulator(executable_path, arguments, environment);
+ if (!emulator.load_elf())
+ return 1;
+
+ StringBuilder builder;
+ builder.append("(UE) ");
+ builder.append(LexicalPath(arguments[0]).basename());
+ if (set_process_name(builder.string_view().characters_without_null_termination(), builder.string_view().length()) < 0) {
+ perror("set_process_name");
+ return 1;
+ }
+ int rc = pthread_setname_np(pthread_self(), builder.to_string().characters());
+ if (rc != 0) {
+ reportln("pthread_setname_np: {}", strerror(rc));
+ return 1;
+ }
+ return emulator.exec();
+}