├── tools └── CMakeLists.txt ├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── qt ├── CMakeLists.txt ├── logo.png └── emilpro │ ├── CMakeLists.txt │ ├── main.cc │ ├── instruction_delegate.hh │ ├── highlighter.hh │ ├── jump_lane_delegate.hh │ ├── instruction_delegate.cc │ ├── jump_lane_delegate.cc │ ├── highlighter.cc │ ├── mainwindow.hh │ ├── mainwindow.ui │ └── mainwindow.cc ├── doc └── emilpro.png ├── .gitattributes ├── test ├── unittest │ ├── main.cc │ ├── test.h │ ├── CMakeLists.txt │ ├── test_address_history.cc │ └── test_lanes.cc └── CMakeLists.txt ├── conanprofile-linux.txt ├── conanprofile-macos.txt ├── src ├── address_history │ ├── CMakeLists.txt │ └── address_history.cc ├── CMakeLists.txt ├── database │ ├── CMakeLists.txt │ └── database.cc ├── jump_lanes │ ├── lane_state_machine.pu │ ├── CMakeLists.txt │ └── jump_lanes.cc ├── disassembly │ ├── CMakeLists.txt │ ├── capstone_disassembler.hh │ └── capstone_disassembler.cc ├── symbol │ ├── CMakeLists.txt │ ├── include │ │ └── symbol.hh │ └── symbol.cc ├── section │ ├── CMakeLists.txt │ ├── include │ │ └── section.hh │ └── section.cc └── binary_parser │ ├── CMakeLists.txt │ ├── binary_parser_factory.cc │ ├── bfd_binary_parser.hh │ └── bfd_binary_parser.cc ├── conanfile.txt ├── include └── emilpro │ ├── mock │ ├── mock_binary_parser.hh │ ├── mock_disassembler.hh │ ├── mock_symbol.hh │ ├── mock_section.hh │ └── mock_instruction.hh │ ├── machine.hh │ ├── i_binary_parser.hh │ ├── i_disassembler.hh │ ├── address_history.hh │ ├── i_symbol.hh │ ├── i_section.hh │ ├── database.hh │ ├── i_instruction.hh │ └── jump_lanes.hh ├── cmake └── Findbfd.cmake ├── .clang-format ├── LICENSE ├── CMakeLists.txt └── README.md /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pro* 3 | build/ 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [SimonKagstrom] 2 | -------------------------------------------------------------------------------- /qt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(emilpro) 2 | -------------------------------------------------------------------------------- /qt/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimonKagstrom/emilpro/HEAD/qt/logo.png -------------------------------------------------------------------------------- /doc/emilpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimonKagstrom/emilpro/HEAD/doc/emilpro.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /conanfile.txt linguist-language=INI 2 | /conanprofile-*.txt linguist-language=INI 3 | -------------------------------------------------------------------------------- /test/unittest/main.cc: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include "doctest/doctest.h" 3 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(trompeloeil REQUIRED) 2 | find_package(doctest REQUIRED) 3 | 4 | add_subdirectory(unittest) 5 | -------------------------------------------------------------------------------- /test/unittest/test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | using trompeloeil::_; 7 | -------------------------------------------------------------------------------- /conanprofile-linux.txt: -------------------------------------------------------------------------------- 1 | [settings] 2 | os=Linux 3 | arch=x86_64 4 | compiler=gcc 5 | compiler.version=13 6 | compiler.libcxx=libstdc++11 7 | build_type=Debug 8 | -------------------------------------------------------------------------------- /conanprofile-macos.txt: -------------------------------------------------------------------------------- 1 | [settings] 2 | os=Macos 3 | arch=x86_64 4 | compiler=apple-clang 5 | compiler.version=14 6 | compiler.libcxx=libc++ 7 | build_type=Debug 8 | -------------------------------------------------------------------------------- /src/address_history/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(address_history EXCLUDE_FROM_ALL 2 | address_history.cc 3 | ) 4 | 5 | target_link_libraries(address_history 6 | PUBLIC 7 | emilpro_interface 8 | ) 9 | -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | doctest/2.4.11 3 | trompeloeil/48 4 | fmt/11.0.2 5 | capstone/5.0.1 6 | etl/20.39.4 7 | libiberty/9.1.0 8 | 9 | [generators] 10 | CMakeDeps 11 | CMakeToolchain 12 | 13 | [layout] 14 | cmake_layout 15 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(address_history) 2 | add_subdirectory(binary_parser) 3 | add_subdirectory(database) 4 | add_subdirectory(disassembly) 5 | add_subdirectory(jump_lanes) 6 | add_subdirectory(section) 7 | add_subdirectory(symbol) 8 | -------------------------------------------------------------------------------- /src/database/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(database EXCLUDE_FROM_ALL 2 | database.cc 3 | ) 4 | 5 | target_link_libraries(database 6 | PUBLIC 7 | emilpro_interface 8 | PRIVATE 9 | fmt::fmt 10 | capstone_disassembler 11 | binary_parser_factory 12 | ) 13 | -------------------------------------------------------------------------------- /src/jump_lanes/lane_state_machine.pu: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | state kNone 4 | state kStart 5 | state kEnd 6 | state kTraffic 7 | 8 | [*] --> kNone 9 | 10 | kNone --> kStart : refers_to X 11 | kStart --> kTraffic 12 | kTraffic --> kEnd : referenced_by X 13 | 14 | @enduml 15 | -------------------------------------------------------------------------------- /src/disassembly/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(capstone_disassembler EXCLUDE_FROM_ALL 2 | capstone_disassembler.cc 3 | ) 4 | 5 | target_link_libraries(capstone_disassembler 6 | PUBLIC 7 | emilpro_interface 8 | capstone::capstone 9 | PRIVATE 10 | fmt::fmt 11 | etl::etl 12 | ) 13 | -------------------------------------------------------------------------------- /src/symbol/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(symbol EXCLUDE_FROM_ALL 2 | symbol.cc 3 | ) 4 | 5 | target_link_libraries(symbol 6 | PUBLIC 7 | emilpro_interface 8 | PRIVATE 9 | p::bfd 10 | fmt::fmt 11 | ) 12 | 13 | target_include_directories(symbol 14 | PUBLIC 15 | include 16 | ) 17 | -------------------------------------------------------------------------------- /src/jump_lanes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(jump_lanes EXCLUDE_FROM_ALL 2 | jump_lanes.cc 3 | ) 4 | 5 | target_link_libraries(jump_lanes 6 | PUBLIC 7 | emilpro_interface 8 | etl::etl 9 | PRIVATE 10 | fmt::fmt 11 | ) 12 | 13 | target_include_directories(jump_lanes 14 | PUBLIC 15 | include 16 | ) 17 | -------------------------------------------------------------------------------- /src/section/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(section EXCLUDE_FROM_ALL 2 | section.cc 3 | ) 4 | 5 | target_link_libraries(section 6 | PUBLIC 7 | emilpro_interface 8 | symbol 9 | etl::etl 10 | PRIVATE 11 | fmt::fmt 12 | ) 13 | 14 | target_include_directories(section 15 | PUBLIC 16 | include 17 | ) 18 | -------------------------------------------------------------------------------- /test/unittest/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(ut 2 | test_address_history.cc 3 | test_lanes.cc 4 | main.cc 5 | ) 6 | 7 | target_link_libraries(ut 8 | trompeloeil::trompeloeil 9 | doctest::doctest 10 | address_history 11 | database 12 | jump_lanes 13 | fmt::fmt 14 | ) 15 | 16 | add_test(NAME unittest COMMAND ut) 17 | -------------------------------------------------------------------------------- /include/emilpro/mock/mock_binary_parser.hh: -------------------------------------------------------------------------------- 1 | #include "../i_binary_parser.hh" 2 | 3 | #include 4 | 5 | namespace emilpro::mock 6 | { 7 | class MockBinaryParser : public IBinaryParser 8 | { 9 | public: 10 | MAKE_CONST_MOCK0(GetMachine, Machine(), final); 11 | MAKE_MOCK1(ForAllSections, void(const std::function)>&), final); 12 | }; 13 | 14 | } // namespace emilpro::mock 15 | -------------------------------------------------------------------------------- /src/binary_parser/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(bfd_binary_parser EXCLUDE_FROM_ALL 2 | bfd_binary_parser.cc 3 | ) 4 | 5 | target_link_libraries(bfd_binary_parser 6 | PUBLIC 7 | emilpro_interface 8 | p::bfd 9 | libiberty::libiberty 10 | fmt::fmt 11 | symbol 12 | section 13 | ) 14 | 15 | 16 | add_library(binary_parser_factory EXCLUDE_FROM_ALL 17 | binary_parser_factory.cc 18 | ) 19 | 20 | target_link_libraries(binary_parser_factory 21 | PRIVATE 22 | bfd_binary_parser 23 | ) 24 | -------------------------------------------------------------------------------- /qt/emilpro/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_library(mainwindow EXCLUDE_FROM_ALL 3 | highlighter.cc 4 | instruction_delegate.cc 5 | jump_lane_delegate.cc 6 | mainwindow.ui 7 | mainwindow.cc 8 | ) 9 | 10 | target_link_libraries(mainwindow 11 | PUBLIC 12 | Qt6::Widgets 13 | fmt::fmt 14 | database 15 | address_history 16 | jump_lanes 17 | ) 18 | 19 | add_executable(emilpro 20 | main.cc 21 | ) 22 | 23 | target_link_libraries(emilpro 24 | mainwindow 25 | binary_parser_factory 26 | capstone_disassembler 27 | ) 28 | -------------------------------------------------------------------------------- /include/emilpro/machine.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace emilpro 8 | { 9 | 10 | enum class Machine : uint8_t 11 | { 12 | k8086, 13 | kI386, 14 | kX86_64, 15 | kArm, 16 | kArmThumb, // ARM in thumb mode 17 | kArm64, 18 | kMips, 19 | kPpc, 20 | kRiscV, 21 | 22 | kUnknown, 23 | }; 24 | 25 | const char* MachineToString(Machine machine); 26 | std::optional MachineFromString(std::string_view str); 27 | 28 | } // namespace emilpro 29 | -------------------------------------------------------------------------------- /include/emilpro/mock/mock_disassembler.hh: -------------------------------------------------------------------------------- 1 | #include "../i_disassembler.hh" 2 | 3 | #include 4 | 5 | namespace emilpro::mock 6 | { 7 | class MockDisassembler : public IDisassembler 8 | { 9 | public: 10 | MAKE_MOCK5(Disassemble, 11 | void(const ISection& section, 12 | const ISymbol* symbol, 13 | uint64_t start_address, 14 | std::span data, 15 | std::function)> on_instruction), 16 | final); 17 | }; 18 | 19 | } // namespace emilpro::mock -------------------------------------------------------------------------------- /include/emilpro/i_binary_parser.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "i_section.hh" 4 | #include "machine.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace emilpro 12 | { 13 | 14 | class IBinaryParser 15 | { 16 | public: 17 | virtual ~IBinaryParser() = default; 18 | 19 | virtual void 20 | ForAllSections(const std::function)> &on_section) = 0; 21 | 22 | virtual Machine GetMachine() const = 0; 23 | 24 | static std::unique_ptr FromFile(std::string_view path, 25 | std::optional machine_hint = std::nullopt); 26 | }; 27 | 28 | } // namespace emilpro 29 | -------------------------------------------------------------------------------- /qt/emilpro/main.cc: -------------------------------------------------------------------------------- 1 | #include "mainwindow.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace 9 | { 10 | 11 | void 12 | Usage(auto app_name) 13 | { 14 | fmt::print("Usage: {} [filename]\n", app_name); 15 | exit(1); 16 | } 17 | 18 | } // namespace 19 | 20 | int 21 | main(int argc, char* argv[]) 22 | { 23 | QApplication a(argc, argv); 24 | 25 | if (argc > 1 && !QFile(argv[1]).exists()) 26 | { 27 | fmt::print("File {} not found\n\n", argv[1]); 28 | Usage(argv[0]); 29 | } 30 | 31 | MainWindow w; 32 | 33 | w.show(); 34 | 35 | if (argc > 1) 36 | { 37 | w.TriggerOpenFile(argv[1]); 38 | } 39 | return QApplication::exec(); 40 | } 41 | -------------------------------------------------------------------------------- /qt/emilpro/instruction_delegate.hh: -------------------------------------------------------------------------------- 1 | // From https://stackoverflow.com/questions/1956542/how-to-make-item-view-render-rich-html-text-in-qt/1956781#1956781 2 | #pragma once 3 | 4 | #include "emilpro/i_instruction.hh" 5 | 6 | #include 7 | #include 8 | 9 | class InstructionDelegate : public QStyledItemDelegate 10 | { 11 | public: 12 | void HighlightStrings(std::span highlight_strings); 13 | 14 | private: 15 | void paint(QPainter* painter, 16 | const QStyleOptionViewItem& option, 17 | const QModelIndex& index) const final; 18 | QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const final; 19 | 20 | std::vector m_highlight_strings; 21 | }; 22 | -------------------------------------------------------------------------------- /include/emilpro/i_disassembler.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "i_instruction.hh" 4 | #include "i_section.hh" 5 | #include "i_symbol.hh" 6 | #include "machine.hh" 7 | 8 | #include 9 | #include 10 | 11 | namespace emilpro 12 | { 13 | 14 | class IDisassembler 15 | { 16 | public: 17 | virtual ~IDisassembler() = default; 18 | 19 | virtual void Disassemble(const ISection& section, 20 | const ISymbol* symbol, 21 | uint64_t start_address, 22 | std::span data, 23 | std::function)> on_instruction) = 0; 24 | 25 | static std::unique_ptr CreateFromArchitecture(Machine machine); 26 | }; 27 | 28 | } // namespace emilpro 29 | -------------------------------------------------------------------------------- /src/disassembly/capstone_disassembler.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "emilpro/i_disassembler.hh" 4 | 5 | #include 6 | 7 | namespace emilpro 8 | { 9 | 10 | class CapstoneDisassembler : public IDisassembler 11 | { 12 | public: 13 | static std::unique_ptr Create(Machine machine); 14 | 15 | ~CapstoneDisassembler() final; 16 | 17 | private: 18 | void Disassemble(const ISection& section, 19 | const ISymbol* symbol, 20 | uint64_t start_address, 21 | std::span data, 22 | std::function)> on_instruction) final; 23 | 24 | CapstoneDisassembler(cs_arch arch, cs_mode mode); 25 | csh m_handle; 26 | cs_arch m_arch; 27 | }; 28 | 29 | } // namespace emilpro -------------------------------------------------------------------------------- /cmake/Findbfd.cmake: -------------------------------------------------------------------------------- 1 | # From https://gitlab.com/lfortran/lfortran/-/blob/master/cmake/FindBFD.cmake 2 | find_path(BFD_INCLUDE_DIR bfd.h) 3 | 4 | find_library(OPCODES_LIBRARY opcodes) 5 | find_library(SFRAME_LIBRARY sframe) 6 | find_library(ZSTD_LIBRARY zstd) 7 | find_library(BFD_LIBRARY bfd) 8 | 9 | include(FindPackageHandleStandardArgs) 10 | find_package_handle_standard_args(bfd DEFAULT_MSG BFD_INCLUDE_DIR BFD_LIBRARY) 11 | 12 | add_library(p::bfd INTERFACE IMPORTED) 13 | 14 | set_property(TARGET p::bfd PROPERTY INTERFACE_INCLUDE_DIRECTORIES 15 | ${BFD_INCLUDE_DIR} 16 | ) 17 | 18 | if (APPLE) 19 | set_property(TARGET p::bfd PROPERTY INTERFACE_LINK_LIBRARIES 20 | ${BFD_LIBRARY} 21 | # On MacOS, these are not linked automatically 22 | ${OPCODES_LIBRARY} 23 | ${SFRAME_LIBRARY} 24 | ${ZSTD_LIBRARY} 25 | z 26 | ) 27 | else() 28 | set_property(TARGET p::bfd PROPERTY INTERFACE_LINK_LIBRARIES 29 | ${BFD_LIBRARY} 30 | ) 31 | endif() 32 | -------------------------------------------------------------------------------- /include/emilpro/address_history.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "i_section.hh" 4 | 5 | #include 6 | 7 | namespace emilpro 8 | { 9 | 10 | class AddressHistory 11 | { 12 | public: 13 | struct Entry 14 | { 15 | const ISection* section {nullptr}; 16 | uint64_t offset; 17 | uint32_t row {0}; 18 | 19 | bool operator==(const Entry& other) const = default; 20 | }; 21 | 22 | AddressHistory(); 23 | 24 | void PushEntry(const ISection& section, uint64_t offset, uint32_t row); 25 | 26 | void SetIndex(int index); 27 | 28 | void Forward() 29 | { 30 | SetIndex(m_index + 1); 31 | } 32 | 33 | void Backward() 34 | { 35 | SetIndex(m_index - 1); 36 | } 37 | 38 | unsigned CurrentIndex() const; 39 | 40 | void Clear(); 41 | 42 | // Valid until a new entry is pushed 43 | std::span Entries() const; 44 | 45 | private: 46 | std::vector m_entries; 47 | int m_index {0}; 48 | }; 49 | 50 | } // namespace emilpro 51 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | BasedOnStyle: LLVM 3 | AllowShortFunctionsOnASingleLine: None 4 | AlwaysBreakAfterDefinitionReturnType: All 5 | AlwaysBreakAfterReturnType: TopLevelDefinitions 6 | BreakBeforeBraces: Custom 7 | BraceWrapping: 8 | AfterControlStatement: true 9 | AfterFunction: true 10 | AfterEnum: true 11 | AfterStruct: true 12 | AfterUnion: true 13 | BeforeElse: true 14 | AfterNamespace: true 15 | AfterClass: true 16 | IndentWidth: 4 17 | TabWidth: 4 18 | MaxEmptyLinesToKeep: 2 19 | PointerAlignment: Left 20 | UseTab: Never 21 | 22 | ColumnLimit: 100 23 | IncludeBlocks: Regroup 24 | AlignConsecutiveMacros: true 25 | SpaceBeforeParens: true 26 | ReflowComments: false 27 | 28 | AllowAllParametersOfDeclarationOnNextLine: true 29 | BinPackArguments: false 30 | BinPackParameters: false 31 | 32 | AlwaysBreakTemplateDeclarations: Yes 33 | IndentCaseLabels: false 34 | 35 | NamespaceIndentation: None 36 | AccessModifierOffset: -4 37 | 38 | SpaceBeforeCpp11BracedList: true 39 | BreakConstructorInitializers: BeforeComma 40 | -------------------------------------------------------------------------------- /include/emilpro/mock/mock_symbol.hh: -------------------------------------------------------------------------------- 1 | #include "../i_symbol.hh" 2 | 3 | #include 4 | 5 | namespace emilpro::mock 6 | { 7 | 8 | class MockSymbol : public ISymbol 9 | { 10 | public: 11 | MAKE_CONST_MOCK0(Name, const std::string&(), final); 12 | MAKE_CONST_MOCK0(DemangledName, const std::string&(), final); 13 | MAKE_CONST_MOCK0(Offset, uint64_t(), final); 14 | MAKE_CONST_MOCK0(Size, size_t(), final); 15 | MAKE_CONST_MOCK0(Section, (const ISection&)(), final); 16 | MAKE_CONST_MOCK0(Alias, (const ISymbol*)(), final); 17 | MAKE_CONST_MOCK0(Data, std::span(), final); 18 | MAKE_CONST_MOCK0(Flags, const std::string&(), final); 19 | MAKE_CONST_MOCK0(Instructions, 20 | (std::span>)(), 21 | final); 22 | MAKE_CONST_MOCK0(ReferredBy, std::span(), final); 23 | MAKE_CONST_MOCK0(RefersTo, std::span(), final); 24 | 25 | MAKE_MOCK0(WaitForCommit, void()); 26 | }; 27 | 28 | } // namespace emilpro::mock 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Simon Kågström 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/address_history/address_history.cc: -------------------------------------------------------------------------------- 1 | #include "emilpro/address_history.hh" 2 | 3 | using namespace emilpro; 4 | 5 | AddressHistory::AddressHistory() 6 | { 7 | // Avoid reallocations for the span 8 | m_entries.reserve(512); 9 | } 10 | 11 | void 12 | AddressHistory::PushEntry(const ISection& section, uint64_t offset, uint32_t row) 13 | { 14 | if (m_entries.empty() == false && m_index != m_entries.size() - 1) 15 | { 16 | m_entries.resize(m_index); 17 | } 18 | m_entries.push_back({§ion, offset, row}); 19 | m_index = m_entries.size() - 1; 20 | } 21 | 22 | void 23 | AddressHistory::SetIndex(int index) 24 | { 25 | if (index >= 0) 26 | { 27 | m_index = std::min(static_cast(index), m_entries.size() - 1); 28 | } 29 | } 30 | 31 | unsigned 32 | AddressHistory::CurrentIndex() const 33 | { 34 | return m_index; 35 | } 36 | 37 | void 38 | AddressHistory::Clear() 39 | { 40 | m_entries.clear(); 41 | m_index = 0; 42 | } 43 | 44 | // Valid until a new entry is pushed 45 | std::span 46 | AddressHistory::Entries() const 47 | { 48 | return m_entries; 49 | } 50 | -------------------------------------------------------------------------------- /qt/emilpro/highlighter.hh: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | QT_BEGIN_NAMESPACE 10 | class QTextDocument; 11 | QT_END_NAMESPACE 12 | 13 | //! [0] 14 | class Highlighter : public QSyntaxHighlighter 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | Highlighter(QTextDocument* parent = nullptr); 20 | 21 | protected: 22 | void highlightBlock(const QString& text) override; 23 | 24 | private: 25 | struct HighlightingRule 26 | { 27 | QRegularExpression pattern; 28 | QTextCharFormat format; 29 | }; 30 | QList highlightingRules; 31 | 32 | QRegularExpression commentStartExpression; 33 | QRegularExpression commentEndExpression; 34 | 35 | QTextCharFormat keywordFormat; 36 | QTextCharFormat classFormat; 37 | QTextCharFormat singleLineCommentFormat; 38 | QTextCharFormat multiLineCommentFormat; 39 | QTextCharFormat quotationFormat; 40 | QTextCharFormat functionFormat; 41 | }; 42 | //! [0] 43 | -------------------------------------------------------------------------------- /include/emilpro/mock/mock_section.hh: -------------------------------------------------------------------------------- 1 | #include "../i_disassembler.hh" 2 | #include "../i_section.hh" 3 | 4 | #include 5 | 6 | namespace emilpro::mock 7 | { 8 | class MockSection : public ISection 9 | { 10 | public: 11 | MAKE_CONST_MOCK0(Data, (std::span)(), final); 12 | MAKE_CONST_MOCK0(StartAddress, uint64_t(), final); 13 | MAKE_CONST_MOCK0(Size, size_t(), final); 14 | MAKE_CONST_MOCK0(GetType, Type(), final); 15 | MAKE_CONST_MOCK0(Flags, const std::string&(), final); 16 | MAKE_CONST_MOCK0(Name, const std::string&(), final); 17 | MAKE_CONST_MOCK0(Instructions, 18 | (std::span>)(), 19 | final); 20 | MAKE_CONST_MOCK0(Symbols, (std::span>)(), final); 21 | MAKE_CONST_MOCK1(ContainsAddress, bool(uint64_t), final); 22 | MAKE_MOCK1(Disassemble, void(IDisassembler&), final); 23 | MAKE_MOCK0(FixupCrossReferences, void(), final); 24 | MAKE_CONST_MOCK1(InstructionAt, IInstruction*(uint64_t), final); 25 | 26 | MAKE_CONST_MOCK1(DisassemblyHint, void(ISymbol& sym)); 27 | }; 28 | 29 | } // namespace emilpro::mock 30 | -------------------------------------------------------------------------------- /qt/emilpro/jump_lane_delegate.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "emilpro/jump_lanes.hh" 4 | 5 | #include 6 | 7 | class JumpLaneDelegate : public QItemDelegate 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | enum class Direction 13 | { 14 | kForward, 15 | kBackward, 16 | }; 17 | 18 | JumpLaneDelegate(Direction direction, QObject* parent = 0); 19 | 20 | void Update(unsigned max_distance, 21 | std::span> instructions); 22 | 23 | private: 24 | void paint(QPainter* painter, 25 | const QStyleOptionViewItem& option, 26 | const QModelIndex& index) const final; 27 | 28 | QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const final; 29 | 30 | void DrawLine(QPainter* painter, int x, int w, QRect* rect) const; 31 | 32 | void DrawLineStart(QPainter* painter, Direction direction, int x, int w, QRect* rect) const; 33 | 34 | void DrawLineEnd(QPainter* painter, Direction direction, int x, int w, QRect* rect) const; 35 | 36 | const Direction m_direction; 37 | const unsigned int m_lane_width; 38 | emilpro::JumpLanes m_jump_lanes; 39 | }; 40 | -------------------------------------------------------------------------------- /include/emilpro/i_symbol.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "i_instruction.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace emilpro 10 | { 11 | 12 | class ISection; 13 | 14 | class ISymbol 15 | { 16 | public: 17 | virtual ~ISymbol() = default; 18 | 19 | virtual std::span Data() const = 0; 20 | 21 | virtual const ISection& Section() const = 0; 22 | 23 | /// Relative to the section 24 | virtual uint64_t Offset() const = 0; 25 | 26 | virtual size_t Size() const = 0; 27 | 28 | virtual const std::string& Flags() const = 0; 29 | 30 | virtual const std::string& Name() const = 0; 31 | 32 | virtual const std::string& DemangledName() const = 0; 33 | 34 | virtual std::span ReferredBy() const = 0; 35 | 36 | virtual std::span RefersTo() const = 0; 37 | 38 | virtual std::span> Instructions() const = 0; 39 | 40 | /** 41 | * @brief A pointer to an alias for this symbol (dynamic + static for example) 42 | * 43 | * @return the other symbol, or nullptr for none 44 | */ 45 | virtual const ISymbol* Alias() const = 0; 46 | 47 | virtual void WaitForCommit() = 0; 48 | }; 49 | 50 | } // namespace emilpro 51 | -------------------------------------------------------------------------------- /include/emilpro/mock/mock_instruction.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../i_instruction.hh" 4 | #include "../i_section.hh" 5 | 6 | #include 7 | 8 | namespace emilpro::mock 9 | { 10 | 11 | class MockInstruction : public IInstruction 12 | { 13 | public: 14 | MAKE_CONST_MOCK0(Data, (std::span)(), final); 15 | MAKE_CONST_MOCK0(Size, uint32_t(), final); 16 | MAKE_CONST_MOCK0(Type, InstructionType(), final); 17 | MAKE_CONST_MOCK0(Offset, uint64_t(), final); 18 | MAKE_CONST_MOCK0(AsString, std::string_view(), final); 19 | MAKE_CONST_MOCK0(ReferredBy, std::span(), final); 20 | MAKE_CONST_MOCK0(RefersTo, std::optional(), final); 21 | MAKE_CONST_MOCK0(UsedRegisters, std::span(), final); 22 | MAKE_CONST_MOCK0(GetSourceLocation, 23 | (std::optional>)(), 24 | final); 25 | MAKE_CONST_MOCK0(Section, (const ISection&)(), final); 26 | MAKE_CONST_MOCK0(Symbol, (const ISymbol*)(), final); 27 | 28 | MAKE_MOCK3(SetRefersTo, void(const ISection&, uint64_t, const ISymbol*), final); 29 | MAKE_MOCK3(AddReferredBy, 30 | void(const ISection& section, uint64_t offset, const ISymbol* symbol), 31 | final); 32 | MAKE_MOCK2(SetSourceLocation, void(std::string_view, uint32_t), final); 33 | MAKE_MOCK0(Commit, void(), final); 34 | }; 35 | 36 | } // namespace emilpro::mock -------------------------------------------------------------------------------- /include/emilpro/i_section.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace emilpro 8 | { 9 | 10 | class IDisassembler; 11 | class IInstruction; 12 | class ISymbol; 13 | 14 | class ISection 15 | { 16 | public: 17 | enum class Type 18 | { 19 | kData, 20 | kInstructions, 21 | kOther, 22 | }; 23 | 24 | virtual ~ISection() = default; 25 | 26 | virtual void Disassemble(IDisassembler& disassembler) = 0; 27 | 28 | virtual std::span> Instructions() const = 0; 29 | 30 | /// Symbols for this section, sorted by offset 31 | virtual std::span> Symbols() const = 0; 32 | 33 | virtual std::span Data() const = 0; 34 | 35 | virtual const std::string& Name() const = 0; 36 | 37 | virtual uint64_t StartAddress() const = 0; 38 | 39 | virtual size_t Size() const = 0; 40 | 41 | virtual Type GetType() const = 0; 42 | 43 | virtual const std::string& Flags() const = 0; 44 | 45 | virtual bool ContainsAddress(uint64_t address) const = 0; 46 | 47 | virtual IInstruction* InstructionAt(uint64_t) const = 0; 48 | 49 | /// Called after instructions cross-references have been calculated 50 | virtual void FixupCrossReferences() = 0; 51 | 52 | 53 | /// Hint to the disassembler that this symbol is needed urgently 54 | virtual void DisassemblyHint(ISymbol &sym) const = 0; 55 | }; 56 | 57 | } // namespace emilpro 58 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: EmilPRO CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, macos-latest] 11 | 12 | runs-on: ${{ matrix.os }} 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Install dependencies 18 | run: | 19 | if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then 20 | sudo apt-get install -y g++ cmake ninja-build python3-pip binutils-multiarch-dev qt6-base-dev libglx-dev libgl1-mesa-dev 21 | pip3 install conan 22 | else 23 | brew install cmake ninja conan binutils qt6 24 | fi 25 | conan profile detect -f 26 | 27 | - name: Install conan deps 28 | run: conan install -of ${{github.workspace}}/build --build=missing -s build_type=Debug ${{github.workspace}}/conanfile.txt 29 | 30 | - name: Configure CMake 31 | run: | 32 | if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then 33 | cmake -B ${{github.workspace}}/build -GNinja -DCMAKE_PREFIX_PATH="${{github.workspace}}/build/build/Debug/generators/" -DCMAKE_BUILD_TYPE=Debug 34 | else 35 | cmake -B ${{github.workspace}}/build -GNinja -DCMAKE_PREFIX_PATH="${{github.workspace}}/build/build/Debug/generators/;`brew --prefix binutils`" -DCMAKE_BUILD_TYPE=Debug 36 | fi 37 | 38 | - name: Build 39 | run: ninja -C ${{github.workspace}}/build 40 | 41 | - name: Test 42 | run: ctest --verbose --test-dir ${{github.workspace}}/build 43 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.21) 2 | project (emilpro LANGUAGES CXX C ASM) 3 | 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 5 | set(CMAKE_CXX_STANDARD 23) 6 | 7 | # Qt setup 8 | set(CMAKE_AUTOMOC ON) 9 | set(CMAKE_AUTORCC ON) 10 | set(CMAKE_AUTOUIC ON) 11 | 12 | 13 | # Ignore some irritating warnings on MacOS 14 | if (APPLE) 15 | add_link_options(-Wl,-no_warn_duplicate_libraries) 16 | # https://stackoverflow.com/questions/4929255/building-static-libraries-on-mac-using-cmake-and-gcc 17 | set(CMAKE_C_ARCHIVE_CREATE " Scr ") 18 | set(CMAKE_CXX_ARCHIVE_CREATE " Scr ") 19 | set(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") 20 | set(CMAKE_CXX_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") 21 | endif() 22 | 23 | # Enable sanitizers in debug builds 24 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 25 | add_compile_options(-fsanitize=address,undefined -g) 26 | add_link_options(-fsanitize=address,undefined -g) 27 | endif() 28 | 29 | list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 30 | 31 | find_package(fmt REQUIRED) 32 | find_package(capstone REQUIRED) 33 | find_package(libiberty REQUIRED) 34 | find_package(etl REQUIRED) 35 | find_package(bfd REQUIRED) 36 | find_package(Qt6 REQUIRED COMPONENTS Widgets Network) 37 | 38 | add_subdirectory(src) 39 | add_subdirectory(qt) 40 | add_subdirectory(tools) 41 | 42 | # Unit tests are only built in debug mode 43 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 44 | enable_testing() 45 | add_subdirectory(test) 46 | endif() 47 | 48 | add_library(emilpro_interface INTERFACE) 49 | target_include_directories(emilpro_interface INTERFACE include) 50 | -------------------------------------------------------------------------------- /include/emilpro/database.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "emilpro/i_section.hh" 4 | #include "emilpro/i_symbol.hh" 5 | #include "i_binary_parser.hh" 6 | #include "i_disassembler.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace emilpro 14 | { 15 | 16 | class Database 17 | { 18 | public: 19 | ~Database(); 20 | 21 | struct LookupResult 22 | { 23 | const ISection& section; 24 | uint64_t offset; 25 | std::optional> symbol; 26 | std::optional>> instructions; 27 | }; 28 | 29 | bool ParseFile(std::unique_ptr parser, 30 | std::unique_ptr disassembler); 31 | 32 | std::span> Sections() const; 33 | 34 | std::span> Symbols() const; 35 | 36 | std::vector LookupByAddress(const ISection* hint, uint64_t address); 37 | 38 | private: 39 | void ParseThread(); 40 | 41 | void FixupCallRefersTo(IInstruction& instruction, const IInstruction::Referer& referer); 42 | 43 | std::optional> 44 | SymbolBySectionOffset(const ISection& section, uint64_t offset); 45 | 46 | std::vector> m_parsers; 47 | std::unique_ptr m_disassembler; 48 | 49 | std::vector> m_sections; 50 | std::vector> m_section_refs; 51 | 52 | std::vector> m_symbol_refs; 53 | 54 | std::vector> m_instruction_refs; 55 | 56 | std::thread m_parse_thread; 57 | }; 58 | 59 | } // namespace emilpro 60 | -------------------------------------------------------------------------------- /test/unittest/test_address_history.cc: -------------------------------------------------------------------------------- 1 | #include "emilpro/address_history.hh" 2 | #include "emilpro/mock/mock_section.hh" 3 | #include "test.h" 4 | 5 | using namespace emilpro; 6 | 7 | namespace 8 | { 9 | 10 | class Fixture 11 | { 12 | public: 13 | AddressHistory history; 14 | mock::MockSection section; 15 | mock::MockSection section2; 16 | }; 17 | 18 | } // namespace 19 | 20 | TEST_CASE_FIXTURE(Fixture, "the address history is initially empty") 21 | { 22 | REQUIRE(history.Entries().empty()); 23 | } 24 | 25 | TEST_CASE_FIXTURE(Fixture, "an entry can be pushed to the address history") 26 | { 27 | history.PushEntry(section, 13, 1); 28 | REQUIRE(history.CurrentIndex() == 0); 29 | REQUIRE(history.Entries()[0] == AddressHistory::Entry {§ion, 13, 1}); 30 | } 31 | 32 | TEST_CASE_FIXTURE(Fixture, "entries can be selected in the middle of the history") 33 | { 34 | history.PushEntry(section, 13, 1); 35 | history.PushEntry(section2, 14, 2); 36 | history.PushEntry(section, 15, 3); 37 | 38 | REQUIRE(history.CurrentIndex() == 2); 39 | REQUIRE(history.Entries()[0] == AddressHistory::Entry {§ion, 13, 1}); 40 | REQUIRE(history.Entries()[1] == AddressHistory::Entry {§ion2, 14, 2}); 41 | REQUIRE(history.Entries()[2] == AddressHistory::Entry {§ion, 15, 3}); 42 | 43 | WHEN("the first entry is selected") 44 | { 45 | history.SetIndex(1); 46 | REQUIRE(history.CurrentIndex() == 1); 47 | 48 | THEN("a newly pushed entry removes the second") 49 | { 50 | history.PushEntry(section, 99, 5); 51 | REQUIRE(history.Entries().size() == 2); 52 | REQUIRE(history.Entries()[0] == AddressHistory::Entry {§ion, 13, 1}); 53 | REQUIRE(history.Entries()[1] == AddressHistory::Entry {§ion, 99, 5}); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EmilPRO 2 | 3 | A graphical disassembler for multiple architectures. Meant mainly as a debugging tool. 4 | 5 | ![The application on MacOS](doc/emilpro.png) 6 | 7 | Features 🚀 8 | 9 | * Disassembly support for ARM, AArch64, x86, RiscV (basic), (untested) MIPS, (untested) PowerPC 10 | * Easy filtering of symbol names/addresses 11 | * Cross-references for both symbols and instructions 12 | * Navigation via jumps and calls 13 | * History of visited addresses/symbols, with back/forward navigation 14 | * High-level source code display (if available) 15 | * Highlight used registers 16 | 17 | ## Requirements 18 | 19 | ### Debian and Ubuntu 20 | 21 | ``` 22 | sudo apt install g++ cmake ninja-build python3-pip binutils-multiarch-dev qt6-base-dev libglx-dev libgl1-mesa-dev 23 | pip3 install conan 24 | ``` 25 | 26 | ### Fedora 27 | 28 | Still TODO, but more or less like Ubuntu. 29 | 30 | ### MacOS 31 | 32 | ``` 33 | brew install binutils qt6 conan cmake ninja 34 | ``` 35 | 36 | ## Build 37 | 38 | The instructions below are for release builds. For debug builds, replace `Release` with `Debug`, and then 39 | also unit tests will be built. 40 | 41 | ### Conan setup 42 | 43 | ``` 44 | conan install -of build --build=missing -s build_type=Release conanfile.txt 45 | ``` 46 | 47 | ### Linux 48 | 49 | ``` 50 | cmake -B build -GNinja -DCMAKE_PREFIX_PATH="`pwd`/build/build/Release/generators/" -DCMAKE_BUILD_TYPE=Release 51 | ninja -C build 52 | ``` 53 | 54 | ### MacOS 55 | 56 | Remove binutils from the PATH (for the conan build) 57 | 58 | ``` 59 | cmake -B build -GNinja -DCMAKE_PREFIX_PATH="`pwd`build/build/Release/generators/;`brew --prefix binutils`" -DCMAKE_BUILD_TYPE=Release 60 | ninja -C build 61 | ``` 62 | 63 | ## Installation 64 | 65 | ``` 66 | sudo cp qt/emilpro/emilpro /usr/local/bin/ 67 | ``` 68 | 69 | ## Support 70 | 71 | Simon Kagstrom \ 72 | -------------------------------------------------------------------------------- /qt/emilpro/instruction_delegate.cc: -------------------------------------------------------------------------------- 1 | #include "instruction_delegate.hh" 2 | 3 | #include 4 | #include 5 | 6 | void 7 | InstructionDelegate::HighlightStrings(std::span highlight_strings) 8 | 9 | { 10 | m_highlight_strings.clear(); 11 | for (const auto& str : highlight_strings) 12 | { 13 | m_highlight_strings.push_back(QString::fromStdString(str)); 14 | } 15 | } 16 | 17 | void 18 | InstructionDelegate::paint(QPainter* painter, 19 | const QStyleOptionViewItem& option, 20 | const QModelIndex& index) const 21 | { 22 | constexpr auto colors = std::array 23 | { 24 | "red", 25 | "green", 26 | "blue", 27 | "cyan", 28 | }; 29 | 30 | QStyleOptionViewItem options = option; 31 | initStyleOption(&options, index); 32 | 33 | auto encoding = options.text; 34 | auto color_index = 0; 35 | for (const auto& str : m_highlight_strings) 36 | { 37 | QString color = colors[color_index]; 38 | 39 | encoding.replace(str, "" + str + ""); 40 | // Reuse the first color if more than 4 entries 41 | color_index = (color_index + 1) % colors.size(); 42 | } 43 | 44 | painter->save(); 45 | 46 | QTextDocument doc; 47 | doc.setDefaultFont(options.font); 48 | doc.setHtml(encoding); 49 | 50 | options.text = ""; 51 | options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter); 52 | 53 | painter->translate(options.rect.left(), options.rect.top()); 54 | QRect clip(0, 0, options.rect.width(), options.rect.height()); 55 | doc.drawContents(painter, clip); 56 | 57 | painter->restore(); 58 | } 59 | 60 | QSize 61 | InstructionDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const 62 | { 63 | return QSize(); 64 | } -------------------------------------------------------------------------------- /src/binary_parser/binary_parser_factory.cc: -------------------------------------------------------------------------------- 1 | #include "bfd_binary_parser.hh" 2 | #include "emilpro/i_binary_parser.hh" 3 | #include "emilpro/machine.hh" 4 | 5 | #include 6 | #include 7 | 8 | using namespace emilpro; 9 | 10 | constexpr auto kMachineTable = std::array { 11 | std::pair {Machine::k8086, "Intel 8086"}, 12 | std::pair {Machine::kI386, "Intel i386"}, 13 | std::pair {Machine::kX86_64, "Intel x86-64"}, 14 | std::pair {Machine::kArm, "ARM"}, 15 | std::pair {Machine::kArmThumb, "ARM (thumb mode)"}, 16 | std::pair {Machine::kArm64, "ARM64"}, 17 | std::pair {Machine::kMips, "MIPS"}, 18 | std::pair {Machine::kPpc, "PowerPC"}, 19 | std::pair {Machine::kRiscV, "RiscV"}, 20 | }; 21 | static_assert(kMachineTable.size() == std::to_underlying(Machine::kUnknown)); 22 | 23 | // Put them here for now, although not a good place 24 | const char* 25 | emilpro::MachineToString(Machine machine) 26 | { 27 | if (auto it = std::ranges::find_if( 28 | kMachineTable, [machine](const auto& pair) { return pair.first == machine; }); 29 | it != kMachineTable.end()) 30 | { 31 | return it->second; 32 | } 33 | 34 | return ""; 35 | } 36 | 37 | std::optional 38 | emilpro::MachineFromString(std::string_view str) 39 | { 40 | if (auto it = std::ranges::find_if(kMachineTable, 41 | [str](const auto& pair) { return pair.second == str; }); 42 | it != kMachineTable.end()) 43 | { 44 | return it->first; 45 | } 46 | 47 | return std::nullopt; 48 | } 49 | 50 | 51 | std::unique_ptr 52 | IBinaryParser::FromFile(std::string_view path, std::optional machine_hint) 53 | { 54 | auto out = std::make_unique(path, machine_hint); 55 | 56 | if (!out->Parse()) 57 | { 58 | return nullptr; 59 | } 60 | 61 | return out; 62 | } 63 | -------------------------------------------------------------------------------- /src/binary_parser/bfd_binary_parser.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "emilpro/i_binary_parser.hh" 4 | #include "section.hh" 5 | #include "symbol.hh" 6 | 7 | // For libbfd: "error: config.h must be included before this header" 8 | #define PACKAGE 1 9 | #define PACKAGE_VERSION 1 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace emilpro 18 | { 19 | 20 | class BfdBinaryParser : public IBinaryParser 21 | { 22 | public: 23 | explicit BfdBinaryParser(std::string_view path, std::optional machine_hint); 24 | 25 | ~BfdBinaryParser() final; 26 | 27 | bool Parse(); 28 | 29 | private: 30 | Machine GetMachine() const final; 31 | void ForAllSections(const std::function)>& on_section) final; 32 | 33 | std::optional 34 | LookupLine(bfd_section* section, bfd_symbol** symTbl, uint64_t offset); 35 | void HandleSymbols(long symcount, bfd_symbol** syms, bool dynamic); 36 | void HandleRelocations(asection* section, bfd_symbol** syms); 37 | 38 | private: 39 | typedef std::map BfdSectionByAddress_t; 40 | 41 | Machine m_machine {Machine::kUnknown}; 42 | std::string_view m_path; 43 | const std::optional m_machine_hint; 44 | 45 | uint8_t* m_rawData {nullptr}; 46 | size_t m_rawDataSize {0}; 47 | struct bfd* m_bfd {nullptr}; 48 | bfd_symbol** m_bfd_syms {nullptr}; 49 | bfd_symbol** m_dynamic_bfd_syms {nullptr}; 50 | bfd_symbol** m_synthetic_bfd_syms {nullptr}; 51 | bfd_symbol* m_rawSynthetic_bfd_syms {nullptr}; 52 | 53 | std::unordered_map> m_pending_sections; 54 | std::unordered_map m_symbol_map; 55 | 56 | BfdSectionByAddress_t m_sectionByAddress; 57 | 58 | bool m_arm_in_thumb_mode {false}; 59 | }; 60 | 61 | } // namespace emilpro 62 | -------------------------------------------------------------------------------- /include/emilpro/i_instruction.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace emilpro 9 | { 10 | class ISection; 11 | class ISymbol; 12 | 13 | class IInstruction 14 | { 15 | public: 16 | enum class InstructionType 17 | { 18 | kBranch, 19 | kCall, 20 | kOther, 21 | }; 22 | 23 | struct Referer 24 | { 25 | const ISection* section; 26 | uint64_t offset; 27 | const ISymbol* symbol; 28 | }; 29 | 30 | virtual ~IInstruction() = default; 31 | 32 | virtual std::span Data() const = 0; 33 | 34 | virtual InstructionType Type() const = 0; 35 | 36 | virtual uint32_t Size() const = 0; 37 | 38 | virtual uint64_t Offset() const = 0; 39 | 40 | virtual std::string_view AsString() const = 0; 41 | 42 | virtual std::span ReferredBy() const = 0; 43 | 44 | virtual std::optional RefersTo() const = 0; 45 | 46 | virtual std::span UsedRegisters() const = 0; 47 | 48 | virtual std::optional> GetSourceLocation() const = 0; 49 | 50 | virtual const ISection& Section() const = 0; 51 | 52 | virtual const ISymbol* Symbol() const = 0; 53 | 54 | // Modifying methods (used when parsing the binary) 55 | 56 | /** 57 | * @brief Set the high-level source file for this instruction 58 | * 59 | * @param file the file 60 | * @param line the line 61 | */ 62 | virtual void SetSourceLocation(std::string_view file, uint32_t line) = 0; 63 | 64 | /** 65 | * @brief Set the symbols/offsets this instruction refers to 66 | * 67 | * @param section the section of the destination 68 | * @param offset the offset of the destination instruction 69 | * @param symbol the symbol (if resolved) of the destination 70 | */ 71 | virtual void SetRefersTo(const ISection& section, uint64_t offset, const ISymbol* symbol) = 0; 72 | 73 | /** 74 | * @brief Set the incoming references for this instruction 75 | * 76 | * @param section the source section of the reference 77 | * @param offset the source offset of the reference 78 | * @param symbol the source symbol of the reference 79 | */ 80 | virtual void AddReferredBy(const ISection& section, uint64_t offset, const ISymbol* symbol) = 0; 81 | 82 | /** 83 | * @brief Commit the modifiable data for this instruction 84 | * 85 | * Until then, @a RefersTo etc will return empty values 86 | */ 87 | virtual void Commit() = 0; 88 | }; 89 | 90 | } // namespace emilpro 91 | -------------------------------------------------------------------------------- /src/symbol/include/symbol.hh: -------------------------------------------------------------------------------- 1 | #include "emilpro/i_section.hh" 2 | #include "emilpro/i_symbol.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #pragma once 10 | 11 | namespace emilpro 12 | { 13 | 14 | class Symbol : public ISymbol 15 | { 16 | public: 17 | Symbol(const ISection& section, uint64_t offset, std::string_view flags, std::string_view name); 18 | 19 | void SetSize(size_t size); 20 | 21 | void AddRelocation(const ISection& src_section, uint64_t offset); 22 | 23 | std::span Data() const final; 24 | const ISection& Section() const final; 25 | uint64_t Offset() const final; 26 | size_t Size() const final; 27 | const std::string& Flags() const final; 28 | const std::string& Name() const final; 29 | const std::string& DemangledName() const final; 30 | std::span> Instructions() const final; 31 | std::span ReferredBy() const final; 32 | std::span RefersTo() const final; 33 | const ISymbol* Alias() const final; 34 | 35 | void WaitForCommit() final; 36 | 37 | // Currently processed instructions (before commit) 38 | std::vector>& InstructionsStore(); 39 | 40 | void SetInstructions(std::span> instructions); 41 | void AddReferredBy(std::span referers); 42 | void AddRefersTo(const IInstruction::Referer& referer); 43 | 44 | void SetAlias(Symbol* alias); 45 | 46 | Symbol *DoGetAlias(); 47 | 48 | // Called when all referrers/referred by are done 49 | void Commit(); 50 | 51 | private: 52 | const ISection& m_section; 53 | const uint64_t m_offset; 54 | size_t m_size {0}; 55 | std::string m_flags; 56 | std::string m_name; 57 | std::string m_demanged_name; 58 | std::span m_data; 59 | 60 | std::vector> m_relocations; 61 | std::vector> m_instructions_store; 62 | std::span> m_instructions; 63 | 64 | std::span m_referred_by; 65 | std::span m_refers_to; 66 | 67 | std::vector m_referred_by_store; 68 | std::vector m_refers_to_store; 69 | Symbol *m_alias; 70 | 71 | mutable std::mutex m_mutex; 72 | 73 | std::atomic_bool m_committed {false}; 74 | std::binary_semaphore m_commit_semaphore {0}; 75 | }; 76 | 77 | } // namespace emilpro 78 | -------------------------------------------------------------------------------- /src/section/include/section.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "emilpro/i_instruction.hh" 4 | #include "emilpro/i_section.hh" 5 | #include "symbol.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace emilpro 14 | { 15 | 16 | class Section : public ISection 17 | { 18 | public: 19 | struct FileLine 20 | { 21 | std::string_view file; 22 | uint32_t line; 23 | }; 24 | 25 | Section(std::string_view name, 26 | std::span data, 27 | uint64_t start_address, 28 | Type type, 29 | std::string_view flags, 30 | std::function(uint64_t offset)> line_lookup); 31 | 32 | void AddSymbol(std::unique_ptr symbol); 33 | void AddRelocation(uint64_t offset, const Symbol& symbol); 34 | void FixupSymbolSizes(); 35 | 36 | void FixupCrossReferences() final; 37 | void DisassemblyHint(ISymbol& sym) const final; 38 | 39 | private: 40 | struct Relocation 41 | { 42 | std::reference_wrapper symbol; 43 | uint64_t offset; 44 | }; 45 | 46 | void Disassemble(IDisassembler& disassembler) final; 47 | 48 | std::span> Instructions() const final; 49 | 50 | std::span> Symbols() const final; 51 | 52 | std::span Data() const final; 53 | 54 | const std::string& Name() const final; 55 | 56 | uint64_t StartAddress() const final; 57 | 58 | size_t Size() const final; 59 | 60 | Type GetType() const final; 61 | 62 | const std::string& Flags() const final; 63 | 64 | bool ContainsAddress(uint64_t address) const final; 65 | 66 | IInstruction* InstructionAt(uint64_t) const final; 67 | 68 | 69 | void DisassembleSymbol(std::vector symbols_at_address, IDisassembler& disassembler); 70 | 71 | const std::vector m_data; 72 | const uint64_t m_start_address; 73 | const Type m_type; 74 | const std::string m_flags; 75 | const std::string m_name; 76 | std::function(uint64_t offset)> m_line_lookup; 77 | 78 | std::vector> m_symbols; 79 | std::vector> m_symbol_refs; 80 | std::map> m_sorted_symbols; 81 | 82 | std::vector> m_relocations; 83 | std::map m_sorted_relocations; 84 | 85 | std::vector> m_instructions; 86 | std::vector> m_instruction_refs; 87 | std::map m_sorted_instructions; 88 | 89 | mutable etl::queue_spsc_atomic m_disassembly_hint_queue; 90 | }; 91 | 92 | } // namespace emilpro 93 | -------------------------------------------------------------------------------- /include/emilpro/jump_lanes.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "i_instruction.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace emilpro 14 | { 15 | 16 | class JumpLanes 17 | { 18 | public: 19 | constexpr static auto kNumberOfLanes = 4; 20 | 21 | enum class LaneState 22 | { 23 | kNone, 24 | kStart, 25 | kTraffic, 26 | kEnd, 27 | }; 28 | 29 | enum class Type 30 | { 31 | kNone, 32 | kStart, 33 | kTraffic, 34 | kEnd, 35 | kStartEnd, // Jump here + start from here 36 | kLongStart, // Start without lanes 37 | kLongEnd, 38 | }; 39 | 40 | struct Lanes 41 | { 42 | Lanes() 43 | { 44 | std::ranges::fill(backward_lanes, Type::kNone); 45 | std::ranges::fill(forward_lanes, Type::kNone); 46 | } 47 | 48 | Lanes(auto backward, auto forward) 49 | : backward_lanes(backward) 50 | , forward_lanes(forward) 51 | { 52 | } 53 | 54 | std::array backward_lanes; 55 | std::array forward_lanes; 56 | }; 57 | 58 | void Calculate(unsigned max_distance, 59 | std::span> instructions); 60 | 61 | std::span GetLanes() const; 62 | 63 | private: 64 | class Lane 65 | { 66 | public: 67 | Lane(uint64_t start, uint64_t end, uint32_t max_distance) 68 | : m_forward(start <= end) 69 | , m_first(m_forward ? start : end) 70 | , m_last(m_forward ? end : start) 71 | , m_max_distance(max_distance) 72 | { 73 | assert(m_last >= m_first); 74 | } 75 | 76 | bool Covers(uint64_t offset) const 77 | { 78 | return offset >= m_first && offset <= m_last; 79 | } 80 | 81 | uint64_t StartsAt() const 82 | { 83 | return m_first; 84 | } 85 | 86 | uint64_t EndsAt() const 87 | { 88 | return m_last; 89 | } 90 | 91 | unsigned LaneNumber() const 92 | { 93 | return m_lane; 94 | } 95 | 96 | void PushOut() 97 | { 98 | m_lane++; 99 | } 100 | 101 | bool IsForward() const 102 | { 103 | return m_forward; 104 | } 105 | 106 | bool Overlaps(const Lane& other) const 107 | { 108 | // Either of the two lanes cross the other 109 | return m_first < other.m_first && m_last > other.m_first || 110 | m_first < other.m_last && m_last > other.m_last; 111 | } 112 | 113 | bool Encloses(const Lane& other) const 114 | { 115 | return m_first < other.m_first && m_last >= other.m_last; 116 | } 117 | 118 | Type Calculate(uint64_t offset) const; 119 | 120 | private: 121 | uint64_t Distance() const 122 | { 123 | return m_last - m_first; 124 | } 125 | 126 | const bool m_forward; 127 | const uint64_t m_first; 128 | const uint64_t m_last; 129 | const uint32_t m_max_distance; 130 | 131 | unsigned m_lane {0}; // The inner lane 132 | }; 133 | 134 | unsigned Distance(const IInstruction& insn, const IInstruction::Referer& referer) const; 135 | 136 | void PushPredecessors(std::vector& lanes) const; 137 | 138 | std::array 139 | Process(const IInstruction& insn, 140 | std::vector& lanes, 141 | etl::vector& current_lanes) const; 142 | 143 | std::vector m_lanes; 144 | }; 145 | 146 | } // namespace emilpro 147 | -------------------------------------------------------------------------------- /qt/emilpro/jump_lane_delegate.cc: -------------------------------------------------------------------------------- 1 | #include "jump_lane_delegate.hh" 2 | 3 | #include 4 | 5 | using namespace emilpro; 6 | 7 | constexpr auto kNumberOfLanes = JumpLanes::kNumberOfLanes; 8 | 9 | JumpLaneDelegate::JumpLaneDelegate(Direction direction, QObject* parent) 10 | : m_direction(direction) 11 | , m_lane_width(80 / kNumberOfLanes) 12 | { 13 | } 14 | 15 | void 16 | JumpLaneDelegate::Update( 17 | unsigned max_distance, 18 | std::span> instructions) 19 | { 20 | m_jump_lanes.Calculate(max_distance, instructions); 21 | } 22 | 23 | QSize 24 | JumpLaneDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const 25 | { 26 | QRect r = option.rect; 27 | 28 | return QSize(80, r.height()); 29 | } 30 | 31 | void 32 | JumpLaneDelegate::paint(QPainter* painter, 33 | const QStyleOptionViewItem& option, 34 | const QModelIndex& index) const 35 | { 36 | const auto color = std::array { 37 | QColor {Qt::green}, 38 | QColor {Qt::cyan}, 39 | QColor {Qt::red}, 40 | QColor {Qt::magenta}, 41 | }; 42 | 43 | int row = index.row(); 44 | QRect r = option.rect; 45 | 46 | auto lanes = m_jump_lanes.GetLanes()[row]; 47 | 48 | for (unsigned lane = 0; lane < kNumberOfLanes; lane++) 49 | { 50 | static_assert(kNumberOfLanes > 0); 51 | const auto& cur = m_direction == Direction::kForward 52 | ? lanes.forward_lanes[lane] 53 | // Reverse order for backward 54 | : lanes.backward_lanes[kNumberOfLanes - lane - 1]; 55 | auto x = r.x() + m_lane_width * lane; 56 | auto w = r.width() / kNumberOfLanes; 57 | 58 | QPen pen(color[lane], Qt::SolidLine); 59 | pen.setWidth(2); 60 | 61 | painter->setPen(pen); 62 | painter->setBrush(QBrush(color[lane])); 63 | 64 | using Type = JumpLanes::Type; 65 | switch (cur) 66 | { 67 | case Type::kTraffic: 68 | DrawLine(painter, x, w, &r); 69 | break; 70 | case Type::kEnd: 71 | DrawLineEnd(painter, m_direction, x, w, &r); 72 | break; 73 | case Type::kStart: 74 | DrawLineStart(painter, m_direction, x, w, &r); 75 | break; 76 | case Type::kLongStart: 77 | pen.setStyle(Qt::DotLine); 78 | painter->setPen(pen); 79 | DrawLineStart(painter, m_direction, x, w, &r); 80 | break; 81 | case Type::kLongEnd: 82 | pen.setStyle(Qt::DotLine); 83 | painter->setPen(pen); 84 | DrawLineEnd(painter, m_direction, x, w, &r); 85 | break; 86 | default: 87 | break; 88 | } 89 | } 90 | } 91 | 92 | void 93 | JumpLaneDelegate::DrawLine(QPainter* painter, int x, int w, QRect* rect) const 94 | { 95 | painter->drawLine(x + w / 2, rect->y(), x + w / 2, rect->y() + rect->height()); 96 | } 97 | 98 | void 99 | JumpLaneDelegate::DrawLineStart( 100 | QPainter* painter, Direction direction, int x, int w, QRect* rect) const 101 | { 102 | const auto startY = rect->y() + rect->height() / 2; 103 | const auto endY = direction == Direction::kBackward ? rect->y() : rect->y() + rect->height(); 104 | const auto startX = x + w / 2; 105 | 106 | painter->drawEllipse(startX - 3, startY - 3, 6, 6); 107 | painter->drawLine(startX, startY, startX, endY); 108 | } 109 | 110 | void 111 | JumpLaneDelegate::DrawLineEnd( 112 | QPainter* painter, Direction direction, int x, int w, QRect* rect) const 113 | { 114 | const auto endY = rect->y() + rect->height() / 2; 115 | const auto startY = direction == Direction::kBackward ? rect->y() + rect->height() : rect->y(); 116 | const auto startX = x + w / 2; 117 | const auto endX = direction == Direction::kBackward ? rect->x() + rect->width() : rect->x(); 118 | const auto arrowDir = direction == Direction::kBackward ? -1 : 1; 119 | QPolygon polygon; 120 | 121 | painter->drawLine(startX, startY, startX, endY); 122 | 123 | polygon << QPoint(startX, endY) << QPoint(endX, endY) << QPoint(endX + 5 * arrowDir, endY - 5) 124 | << QPoint(endX + 5 * arrowDir, endY + 5) << QPoint(endX, endY); 125 | 126 | painter->drawPolygon(polygon); 127 | } 128 | -------------------------------------------------------------------------------- /src/symbol/symbol.cc: -------------------------------------------------------------------------------- 1 | #include "symbol.hh" 2 | 3 | #include 4 | 5 | using namespace emilpro; 6 | 7 | // From libiberty. Including demangle.h conflicts with string.h though 8 | #define DMGL_PARAMS (1 << 0) /* Include function args */ 9 | #define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */ 10 | #define DMGL_VERBOSE (1 << 3) /* Include implementation details. */ 11 | 12 | extern "C" char* cplus_demangle(const char* mangled, int options); 13 | 14 | 15 | Symbol::Symbol(const ISection& section, 16 | uint64_t offset, 17 | std::string_view flags, 18 | std::string_view name) 19 | : m_section(section) 20 | , m_offset(offset) 21 | , m_flags(flags) 22 | , m_name(name) 23 | , m_data(section.Data().subspan(offset)) 24 | , m_alias(this) 25 | { 26 | // Use what c++filt uses... 27 | int demangle_flags = DMGL_PARAMS | DMGL_ANSI | DMGL_VERBOSE; 28 | 29 | auto demangled = cplus_demangle(m_name.c_str(), demangle_flags); 30 | 31 | if (!demangled) 32 | { 33 | // Try the MacOS extra _ as well 34 | demangled = cplus_demangle(m_name.substr(1).c_str(), demangle_flags); 35 | } 36 | 37 | m_demanged_name = demangled ? demangled : m_name; 38 | 39 | free(demangled); 40 | } 41 | 42 | void 43 | Symbol::SetSize(size_t size) 44 | { 45 | size_t max = std::distance(m_section.Data().begin() + m_offset, m_section.Data().end()); 46 | 47 | m_size = std::min(size, max); 48 | m_data = m_section.Data().subspan(m_offset, m_size); 49 | } 50 | 51 | 52 | std::span 53 | Symbol::Data() const 54 | { 55 | return m_data; 56 | } 57 | 58 | const ISection& 59 | Symbol::Section() const 60 | { 61 | return m_section; 62 | } 63 | 64 | uint64_t 65 | Symbol::Offset() const 66 | { 67 | return m_offset; 68 | } 69 | 70 | size_t 71 | Symbol::Size() const 72 | { 73 | return m_size; 74 | } 75 | 76 | const std::string& 77 | Symbol::Flags() const 78 | { 79 | return m_flags; 80 | } 81 | 82 | const std::string& 83 | Symbol::Name() const 84 | { 85 | return m_name; 86 | } 87 | 88 | const std::string& 89 | Symbol::DemangledName() const 90 | { 91 | return m_demanged_name; 92 | } 93 | 94 | void 95 | Symbol::AddRelocation(const ISection& src_section, uint64_t offset) 96 | { 97 | m_relocations.emplace_back(src_section); 98 | } 99 | 100 | std::span> 101 | Symbol::Instructions() const 102 | { 103 | std::scoped_lock lock(m_mutex); 104 | 105 | return m_instructions; 106 | } 107 | 108 | std::span 109 | Symbol::ReferredBy() const 110 | { 111 | std::scoped_lock lock(m_mutex); 112 | 113 | return m_referred_by; 114 | } 115 | 116 | 117 | std::span 118 | Symbol::RefersTo() const 119 | { 120 | std::scoped_lock lock(m_mutex); 121 | 122 | return m_refers_to; 123 | } 124 | 125 | const ISymbol* 126 | Symbol::Alias() const 127 | { 128 | return m_alias; 129 | } 130 | 131 | std::vector>& 132 | Symbol::InstructionsStore() 133 | { 134 | return m_instructions_store; 135 | } 136 | 137 | void 138 | Symbol::AddReferredBy(std::span referers) 139 | { 140 | for (auto& r : referers) 141 | { 142 | m_referred_by_store.emplace_back(r); 143 | } 144 | } 145 | 146 | void 147 | Symbol::AddRefersTo(const IInstruction::Referer& referer) 148 | { 149 | m_refers_to_store.emplace_back(referer); 150 | } 151 | 152 | void 153 | Symbol::SetAlias(Symbol* alias) 154 | { 155 | m_alias = alias; 156 | } 157 | 158 | Symbol* 159 | Symbol::DoGetAlias() 160 | { 161 | return m_alias; 162 | } 163 | 164 | void 165 | Symbol::SetInstructions(std::span> instructions) 166 | { 167 | std::ranges::copy(instructions, std::back_inserter(m_instructions_store)); 168 | } 169 | 170 | void 171 | Symbol::Commit() 172 | { 173 | std::scoped_lock lock(m_mutex); 174 | 175 | m_instructions = m_instructions_store; 176 | if (!m_refers_to_store.empty()) 177 | { 178 | // Don't set unless it was actually changed 179 | m_refers_to = m_refers_to_store; 180 | } 181 | if (!m_referred_by_store.empty()) 182 | { 183 | m_referred_by = m_referred_by_store; 184 | } 185 | 186 | m_committed = true; 187 | m_commit_semaphore.release(); 188 | } 189 | 190 | 191 | void 192 | Symbol::WaitForCommit() 193 | { 194 | if (!m_committed) 195 | { 196 | m_section.DisassemblyHint(*this); 197 | m_commit_semaphore.acquire(); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /qt/emilpro/highlighter.cc: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | #include "highlighter.hh" 5 | 6 | //! [0] 7 | Highlighter::Highlighter(QTextDocument* parent) 8 | : QSyntaxHighlighter(parent) 9 | { 10 | HighlightingRule rule; 11 | 12 | keywordFormat.setForeground(Qt::darkBlue); 13 | keywordFormat.setFontWeight(QFont::Bold); 14 | const QString keywordPatterns[] = { 15 | QStringLiteral("\\bchar\\b"), QStringLiteral("\\bclass\\b"), 16 | QStringLiteral("\\bconst\\b"), QStringLiteral("\\bdouble\\b"), 17 | QStringLiteral("\\benum\\b"), QStringLiteral("\\bexplicit\\b"), 18 | QStringLiteral("\\bfriend\\b"), QStringLiteral("\\binline\\b"), 19 | QStringLiteral("\\bint\\b"), QStringLiteral("\\blong\\b"), 20 | QStringLiteral("\\bnamespace\\b"), QStringLiteral("\\boperator\\b"), 21 | QStringLiteral("\\bprivate\\b"), QStringLiteral("\\bprotected\\b"), 22 | QStringLiteral("\\bpublic\\b"), QStringLiteral("\\bshort\\b"), 23 | QStringLiteral("\\bsignals\\b"), QStringLiteral("\\bsigned\\b"), 24 | QStringLiteral("\\bslots\\b"), QStringLiteral("\\bstatic\\b"), 25 | QStringLiteral("\\bstruct\\b"), QStringLiteral("\\btemplate\\b"), 26 | QStringLiteral("\\btypedef\\b"), QStringLiteral("\\btypename\\b"), 27 | QStringLiteral("\\bunion\\b"), QStringLiteral("\\bunsigned\\b"), 28 | QStringLiteral("\\bvirtual\\b"), QStringLiteral("\\bvoid\\b"), 29 | QStringLiteral("\\bvolatile\\b"), QStringLiteral("\\bbool\\b")}; 30 | for (const QString& pattern : keywordPatterns) 31 | { 32 | rule.pattern = QRegularExpression(pattern); 33 | rule.format = keywordFormat; 34 | highlightingRules.append(rule); 35 | //! [0] //! [1] 36 | } 37 | //! [1] 38 | 39 | //! [2] 40 | classFormat.setFontWeight(QFont::Bold); 41 | classFormat.setForeground(Qt::darkMagenta); 42 | rule.pattern = QRegularExpression(QStringLiteral("\\bQ[A-Za-z]+\\b")); 43 | rule.format = classFormat; 44 | highlightingRules.append(rule); 45 | //! [2] 46 | 47 | //! [3] 48 | singleLineCommentFormat.setForeground(Qt::red); 49 | rule.pattern = QRegularExpression(QStringLiteral("//[^\n]*")); 50 | rule.format = singleLineCommentFormat; 51 | highlightingRules.append(rule); 52 | 53 | multiLineCommentFormat.setForeground(Qt::red); 54 | //! [3] 55 | 56 | //! [4] 57 | quotationFormat.setForeground(Qt::darkGreen); 58 | rule.pattern = QRegularExpression(QStringLiteral("\".*\"")); 59 | rule.format = quotationFormat; 60 | highlightingRules.append(rule); 61 | //! [4] 62 | 63 | //! [5] 64 | functionFormat.setFontItalic(true); 65 | functionFormat.setForeground(Qt::blue); 66 | rule.pattern = QRegularExpression(QStringLiteral("\\b[A-Za-z0-9_]+(?=\\()")); 67 | rule.format = functionFormat; 68 | highlightingRules.append(rule); 69 | //! [5] 70 | 71 | //! [6] 72 | commentStartExpression = QRegularExpression(QStringLiteral("/\\*")); 73 | commentEndExpression = QRegularExpression(QStringLiteral("\\*/")); 74 | } 75 | //! [6] 76 | 77 | //! [7] 78 | void 79 | Highlighter::highlightBlock(const QString& text) 80 | { 81 | for (const HighlightingRule& rule : std::as_const(highlightingRules)) 82 | { 83 | QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); 84 | while (matchIterator.hasNext()) 85 | { 86 | QRegularExpressionMatch match = matchIterator.next(); 87 | setFormat(match.capturedStart(), match.capturedLength(), rule.format); 88 | } 89 | } 90 | //! [7] //! [8] 91 | setCurrentBlockState(0); 92 | //! [8] 93 | 94 | //! [9] 95 | int startIndex = 0; 96 | if (previousBlockState() != 1) 97 | startIndex = text.indexOf(commentStartExpression); 98 | 99 | //! [9] //! [10] 100 | while (startIndex >= 0) 101 | { 102 | //! [10] //! [11] 103 | QRegularExpressionMatch match = commentEndExpression.match(text, startIndex); 104 | int endIndex = match.capturedStart(); 105 | int commentLength = 0; 106 | if (endIndex == -1) 107 | { 108 | setCurrentBlockState(1); 109 | commentLength = text.length() - startIndex; 110 | } 111 | else 112 | { 113 | commentLength = endIndex - startIndex + match.capturedLength(); 114 | } 115 | setFormat(startIndex, commentLength, multiLineCommentFormat); 116 | startIndex = text.indexOf(commentStartExpression, startIndex + commentLength); 117 | } 118 | } 119 | //! [11] 120 | -------------------------------------------------------------------------------- /src/database/database.cc: -------------------------------------------------------------------------------- 1 | #include "emilpro/database.hh" 2 | 3 | #include "emilpro/i_binary_parser.hh" 4 | 5 | #include 6 | 7 | using namespace emilpro; 8 | 9 | bool 10 | Database::ParseFile(std::unique_ptr parser, 11 | std::unique_ptr disassembler) 12 | { 13 | m_sections.clear(); 14 | m_section_refs.clear(); 15 | m_symbol_refs.clear(); 16 | m_instruction_refs.clear(); 17 | m_parsers.clear(); 18 | 19 | m_disassembler = std::move(disassembler); 20 | 21 | parser->ForAllSections([this](auto section) { 22 | std::ranges::copy(section->Symbols(), std::back_inserter(m_symbol_refs)); 23 | 24 | m_sections.push_back(std::move(section)); 25 | m_section_refs.push_back(*m_sections.back()); 26 | }); 27 | m_parsers.push_back(std::move(parser)); 28 | 29 | m_parse_thread = std::thread([this] { ParseThread(); }); 30 | 31 | return true; 32 | } 33 | 34 | Database::~Database() 35 | { 36 | m_parse_thread.join(); 37 | } 38 | 39 | // Context: A separate thread 40 | void 41 | Database::ParseThread() 42 | { 43 | // Disassemble all sections 44 | for (const auto& section : m_sections) 45 | { 46 | section->Disassemble(*m_disassembler); 47 | 48 | std::ranges::copy(section->Instructions(), std::back_inserter(m_instruction_refs)); 49 | } 50 | 51 | // Calculate referenced by 52 | std::vector< 53 | std::tuple, IInstruction::Referer, const ISymbol*>> 54 | all_refers_to; 55 | for (const auto& insn_ref : m_instruction_refs) 56 | { 57 | auto& insn = insn_ref.get(); 58 | auto refers_to = insn.RefersTo(); 59 | if (refers_to) 60 | { 61 | if (!refers_to->symbol && insn.Type() == IInstruction::InstructionType::kCall) 62 | { 63 | FixupCallRefersTo(insn, *refers_to); 64 | } 65 | 66 | all_refers_to.emplace_back(insn, *refers_to, insn.Symbol()); 67 | } 68 | } 69 | 70 | for (const auto& [insn_ref, ref, sym] : all_refers_to) 71 | { 72 | const auto& insn = insn_ref.get(); 73 | const auto& hint = insn.Section(); 74 | 75 | if (hint.ContainsAddress(ref.offset)) 76 | { 77 | if (auto dst = hint.InstructionAt(ref.offset)) 78 | { 79 | dst->AddReferredBy(hint, insn.Offset(), sym); 80 | dst->Commit(); 81 | } 82 | } 83 | } 84 | 85 | // Setup cross-references for all symbols 86 | for (const auto& section : m_sections) 87 | { 88 | section->FixupCrossReferences(); 89 | } 90 | } 91 | 92 | std::span> 93 | Database::Sections() const 94 | { 95 | return m_section_refs; 96 | } 97 | 98 | std::span> 99 | Database::Symbols() const 100 | { 101 | return m_symbol_refs; 102 | } 103 | 104 | std::vector 105 | Database::LookupByAddress(const ISection* hint, uint64_t address) 106 | { 107 | if (hint && hint->ContainsAddress(address)) 108 | { 109 | return { 110 | Database::LookupResult {*hint, 111 | address - hint->StartAddress(), 112 | SymbolBySectionOffset(*hint, address - hint->StartAddress())}}; 113 | } 114 | 115 | for (const auto& section : m_sections) 116 | { 117 | if (section->ContainsAddress(address)) 118 | { 119 | return {Database::LookupResult { 120 | *section, 121 | address - section->StartAddress(), 122 | SymbolBySectionOffset(*section, address - section->StartAddress())}}; 123 | } 124 | } 125 | 126 | return {}; 127 | } 128 | 129 | 130 | std::optional> 131 | Database::SymbolBySectionOffset(const ISection& section, uint64_t offset) 132 | { 133 | // TODO: use std::lower_bound by offset here instead 134 | for (auto& sym_ref : section.Symbols()) 135 | { 136 | const auto& sym = sym_ref.get(); 137 | 138 | if (offset >= sym.Offset() && offset < sym.Offset() + sym.Size()) 139 | { 140 | return sym; 141 | } 142 | } 143 | 144 | return std::nullopt; 145 | } 146 | 147 | void 148 | Database::FixupCallRefersTo(IInstruction& insn, const IInstruction::Referer& refers_to) 149 | { 150 | auto results = LookupByAddress(refers_to.section, refers_to.offset); 151 | 152 | for (auto& cur : results) 153 | { 154 | const ISymbol* sym = nullptr; 155 | 156 | if (cur.symbol) 157 | { 158 | sym = &cur.symbol->get(); 159 | } 160 | 161 | insn.SetRefersTo(cur.section, cur.offset, sym); 162 | insn.Commit(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /qt/emilpro/mainwindow.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "emilpro/address_history.hh" 4 | #include "emilpro/database.hh" 5 | #include "emilpro/i_binary_parser.hh" 6 | #include "emilpro/i_instruction.hh" 7 | #include "highlighter.hh" 8 | #include "instruction_delegate.hh" 9 | #include "jump_lane_delegate.hh" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace Ui 17 | { 18 | class MainWindow; 19 | } 20 | 21 | class MainWindow : public QMainWindow 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | enum class LoadError 27 | { 28 | kFileNotFound, 29 | kParseError, 30 | kUnknownArchitecture, 31 | kValueCount, 32 | }; 33 | 34 | explicit MainWindow(QWidget* parent = nullptr); 35 | ~MainWindow() final; 36 | 37 | void TriggerOpenFile(const char* filename); 38 | 39 | private slots: 40 | void on_symbolTableView_activated(const QModelIndex& index); 41 | void on_symbolTableView_entered(const QModelIndex& index); 42 | 43 | void on_instructionTableView_activated(const QModelIndex& index); 44 | 45 | void on_refersToTableView_activated(const QModelIndex& index); 46 | void on_referredByTableView_activated(const QModelIndex& index); 47 | 48 | void on_addressHistoryListView_activated(const QModelIndex& index); 49 | 50 | void on_sourceTextEdit_cursorPositionChanged(); 51 | 52 | void on_insnCurrentChanged(const QModelIndex& current, const QModelIndex& previous); 53 | 54 | void on_instructionTableView_doubleClicked(const QModelIndex& index); 55 | 56 | void on_action_Open_triggered(bool activated); 57 | 58 | void on_action_Refresh_triggered(bool activated); 59 | 60 | void on_action_Quit_triggered(bool activated); 61 | 62 | // The Go menu 63 | void on_action_Forward_triggered(bool activated); 64 | void on_action_Backward_triggered(bool activated); 65 | void on_action_FocusLocationBar_triggered(bool activated); 66 | void on_action_FocusAddressHistory_triggered(bool activated); 67 | void on_action_ToggleReferenceTab_triggered(bool activated); 68 | void on_action_ToggleSymbolsSections_triggered(bool activated); 69 | 70 | void on_action_About_triggered(bool activated); 71 | 72 | void on_symbolTimerTriggered(); 73 | 74 | void on_locationLineEdit_textChanged(const QString& text); 75 | void on_locationLineEdit_returnPressed(); 76 | 77 | void on_LoadFile(const QString& filename); 78 | 79 | private: 80 | /// Parse a file, and return nullptr if successful, otherwise an error code 81 | std::optional LoadFile(const std::string& filename, 82 | std::optional machine_hint = std::nullopt); 83 | 84 | void SetupSectionView(); 85 | 86 | void SetupSymbolView(); 87 | 88 | void SetupInstructionView(); 89 | 90 | void SetupReferencesView(); 91 | 92 | void SetupAddressHistoryView(); 93 | 94 | void SetupInstructionLabels(); 95 | 96 | void SaveSettings(); 97 | 98 | void RestoreSettings(); 99 | 100 | void OnHistoryIndexChanged(); 101 | 102 | void UpdateInstructionView(const emilpro::ISymbol& symbol, uint64_t offset, uint32_t row = 0); 103 | 104 | void UpdateRefersToView(const emilpro::ISymbol& symbol); 105 | void UpdateRefersToView(const emilpro::IInstruction& insn); 106 | 107 | void UpdateReferredByView(const emilpro::ISymbol& symbol); 108 | void UpdateReferredByView(const emilpro::IInstruction& insn); 109 | 110 | void UpdateSymbolView(const emilpro::ISymbol& symbol); 111 | 112 | void UpdateHistoryView(); 113 | 114 | std::optional SelectArchitecture(); 115 | 116 | const QString& LookupSourceFile(std::string_view); 117 | 118 | // From https://forum.qt.io/topic/76265/set-background-of-specific-row-in-qtableview/2 119 | void SetRowColor(QAbstractItemModel* model, 120 | int row, 121 | const QBrush& color, 122 | const QModelIndex& parent = QModelIndex()); 123 | 124 | bool eventFilter(QObject* watched, QEvent* event) final; 125 | 126 | Ui::MainWindow* m_ui {nullptr}; 127 | QStandardItemModel* m_section_view_model {nullptr}; 128 | QStandardItemModel* m_symbol_view_model {nullptr}; 129 | QStandardItemModel* m_instruction_view_model {nullptr}; 130 | QStandardItemModel* m_refers_to_view_model {nullptr}; 131 | QStandardItemModel* m_referred_by_view_model {nullptr}; 132 | QStandardItemModel* m_address_history_view_model {nullptr}; 133 | 134 | std::unique_ptr m_section_proxy_model; 135 | std::unique_ptr m_symbol_proxy_model; 136 | 137 | const emilpro::ISymbol* m_current_symbol {nullptr}; 138 | 139 | JumpLaneDelegate m_forward_item_delegate; 140 | JumpLaneDelegate m_backward_item_delegate; 141 | InstructionDelegate m_instruction_item_delegate; 142 | 143 | Highlighter* m_highlighter {nullptr}; 144 | std::unordered_map m_source_file_map; 145 | QString m_current_source_file; 146 | 147 | 148 | emilpro::Database m_database; 149 | emilpro::AddressHistory m_address_history; 150 | 151 | std::span> m_visible_instructions; 152 | std::span> m_visible_symbols; 153 | 154 | std::vector m_current_instruction_refers_to; 155 | std::span m_current_referred_by; 156 | std::span m_current_refers_to; 157 | }; 158 | -------------------------------------------------------------------------------- /src/jump_lanes/jump_lanes.cc: -------------------------------------------------------------------------------- 1 | #include "emilpro/jump_lanes.hh" 2 | 3 | #include 4 | #include 5 | 6 | using namespace emilpro; 7 | 8 | 9 | void 10 | JumpLanes::Calculate(unsigned max_distance, 11 | std::span> instructions) 12 | { 13 | m_lanes.clear(); 14 | std::vector> refering_instructions; 15 | 16 | std::vector long_jumps; 17 | 18 | /* 19 | * Rules: 20 | * 21 | * - Max 3 lanes 22 | * - Longer enclsoing branches are pushed out (shorter on the closest lanes) 23 | * - Crossing branches are pushed out 24 | */ 25 | for (auto& insn_ref : instructions) 26 | { 27 | const auto& insn = insn_ref.get(); 28 | auto refers_to = insn.RefersTo(); 29 | 30 | if (refers_to != std::nullopt && insn.Type() == IInstruction::InstructionType::kBranch) 31 | { 32 | if (Distance(insn, *refers_to) > max_distance) 33 | { 34 | long_jumps.emplace_back(insn.Offset(), refers_to->offset, max_distance); 35 | } 36 | else 37 | { 38 | refering_instructions.push_back(insn_ref); 39 | } 40 | } 41 | } 42 | 43 | std::vector forward_lanes; 44 | std::vector backward_lanes; 45 | 46 | for (const auto& insn_ref : refering_instructions) 47 | { 48 | const auto& insn = insn_ref.get(); 49 | auto refers_to = insn.RefersTo(); 50 | 51 | if (insn.Offset() < refers_to->offset) 52 | { 53 | forward_lanes.emplace_back(insn.Offset(), refers_to->offset, max_distance); 54 | PushPredecessors(forward_lanes); 55 | } 56 | } 57 | 58 | // std::ranges::reverse_view, but not on clang/MacOS 59 | for (auto i = refering_instructions.size(); i > 0; i--) 60 | { 61 | const auto& insn_ref = refering_instructions[i - 1]; 62 | const auto& insn = insn_ref.get(); 63 | auto refers_to = insn.RefersTo(); 64 | 65 | if (insn.Offset() > refers_to->offset) 66 | { 67 | backward_lanes.emplace_back(insn.Offset(), refers_to->offset, max_distance); 68 | PushPredecessors(backward_lanes); 69 | } 70 | } 71 | 72 | if (forward_lanes.empty() && backward_lanes.empty() && long_jumps.empty()) 73 | { 74 | m_lanes.resize(instructions.size()); 75 | return; 76 | } 77 | 78 | 79 | etl::vector current_lanes_forward; 80 | etl::vector current_lanes_backward; 81 | 82 | for (auto& insn_ref : instructions) 83 | { 84 | auto fwd = Process(insn_ref.get(), forward_lanes, current_lanes_forward); 85 | auto rev = Process(insn_ref.get(), backward_lanes, current_lanes_backward); 86 | 87 | m_lanes.emplace_back(rev, fwd); 88 | } 89 | 90 | for (auto& lane : long_jumps) 91 | { 92 | if (lane.StartsAt() >= m_lanes.size()) 93 | { 94 | continue; 95 | } 96 | if (lane.IsForward()) 97 | { 98 | m_lanes[lane.StartsAt()].forward_lanes[0] = JumpLanes::Type::kLongStart; 99 | if (lane.EndsAt() < m_lanes.size()) 100 | { 101 | m_lanes[lane.EndsAt()].forward_lanes[0] = JumpLanes::Type::kLongEnd; 102 | } 103 | } 104 | else 105 | { 106 | m_lanes[lane.StartsAt()].backward_lanes[0] = JumpLanes::Type::kLongStart; 107 | if (lane.EndsAt() < m_lanes.size()) 108 | { 109 | m_lanes[lane.EndsAt()].backward_lanes[0] = JumpLanes::Type::kLongEnd; 110 | } 111 | } 112 | } 113 | } 114 | 115 | void 116 | JumpLanes::PushPredecessors(std::vector& lanes) const 117 | { 118 | if (lanes.size() < 2) 119 | { 120 | // Nothing to push in this case 121 | return; 122 | } 123 | 124 | const auto& lane = lanes.back(); 125 | for (auto it = lanes.rbegin() + 1; it != lanes.rend(); ++it) 126 | { 127 | auto& last = *it; 128 | 129 | if (last.Encloses(lane) || last.Overlaps(lane)) 130 | { 131 | last.PushOut(); 132 | } 133 | } 134 | } 135 | 136 | std::array 137 | JumpLanes::Process(const IInstruction& insn, 138 | std::vector& lanes, 139 | etl::vector& current_lanes) const 140 | { 141 | std::array cur {}; 142 | etl::vector to_erase; 143 | auto offset = insn.Offset(); 144 | 145 | for (auto& lane : lanes) 146 | { 147 | if (lane.Covers(offset) && 148 | std::ranges::find(current_lanes, std::to_address(&lane)) == current_lanes.end()) 149 | { 150 | if (!current_lanes.full()) 151 | current_lanes.push_back(std::to_address(&lane)); 152 | } 153 | } 154 | 155 | for (auto lane : current_lanes) 156 | { 157 | if (!lane->Covers(offset)) 158 | { 159 | to_erase.push_back(lane); 160 | } 161 | else if (lane->LaneNumber() < kNumberOfLanes) 162 | { 163 | cur[lane->LaneNumber()] = lane->Calculate(offset); 164 | } 165 | } 166 | for (auto lane : to_erase) 167 | { 168 | current_lanes.erase(std::remove(current_lanes.begin(), current_lanes.end(), lane), 169 | current_lanes.end()); 170 | } 171 | 172 | return cur; 173 | } 174 | 175 | 176 | std::span 177 | JumpLanes::GetLanes() const 178 | { 179 | return m_lanes; 180 | } 181 | 182 | unsigned 183 | JumpLanes::Distance(const IInstruction& insn, const IInstruction::Referer& referer) const 184 | { 185 | return std::abs(static_cast(insn.Offset()) - static_cast(referer.offset)); 186 | } 187 | 188 | 189 | JumpLanes::Type 190 | JumpLanes::Lane::Calculate(uint64_t offset) const 191 | { 192 | auto is_long = Distance() > m_max_distance; 193 | 194 | if (offset == m_first) 195 | { 196 | if (IsForward()) 197 | { 198 | return is_long ? Type::kLongStart : Type::kStart; 199 | } 200 | return is_long ? Type::kLongEnd : Type::kEnd; 201 | } 202 | else if (offset == m_last) 203 | { 204 | if (IsForward()) 205 | { 206 | return is_long ? Type::kLongEnd : Type::kEnd; 207 | } 208 | 209 | return is_long ? Type::kLongStart : Type::kStart; 210 | } 211 | else if (offset > m_first && offset < m_last && !is_long) 212 | { 213 | return Type::kTraffic; 214 | } 215 | 216 | return Type::kNone; 217 | } 218 | -------------------------------------------------------------------------------- /src/section/section.cc: -------------------------------------------------------------------------------- 1 | #include "section.hh" 2 | 3 | #include "emilpro/i_disassembler.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace emilpro; 10 | 11 | Section::Section(std::string_view name, 12 | std::span data, 13 | uint64_t start_address, 14 | Type type, 15 | std::string_view flags, 16 | std::function(uint64_t offset)> line_lookup) 17 | : m_data(data.begin(), data.end()) 18 | , m_start_address(start_address) 19 | , m_type(type) 20 | , m_flags(flags) 21 | , m_name(name) 22 | , m_line_lookup(std::move(line_lookup)) 23 | { 24 | } 25 | 26 | std::span 27 | Section::Data() const 28 | { 29 | return m_data; 30 | } 31 | 32 | uint64_t 33 | Section::StartAddress() const 34 | { 35 | return m_start_address; 36 | } 37 | 38 | size_t 39 | Section::Size() const 40 | { 41 | return m_data.size(); 42 | } 43 | 44 | ISection::Type 45 | Section::GetType() const 46 | { 47 | return m_type; 48 | } 49 | 50 | const std::string& 51 | Section::Flags() const 52 | { 53 | return m_flags; 54 | } 55 | 56 | const std::string& 57 | Section::Name() const 58 | { 59 | return m_name; 60 | } 61 | 62 | 63 | void 64 | Section::AddSymbol(std::unique_ptr symbol) 65 | { 66 | m_sorted_symbols[symbol->Offset()].emplace_back(symbol.get()); 67 | m_symbols.emplace_back(std::move(symbol)); 68 | } 69 | 70 | void 71 | Section::AddRelocation(uint64_t offset, const Symbol& symbol) 72 | { 73 | m_relocations.push_back(std::make_unique(Relocation {symbol, offset})); 74 | m_sorted_relocations[offset] = m_relocations.back().get(); 75 | } 76 | 77 | void 78 | Section::FixupSymbolSizes() 79 | { 80 | size_t last_offset = Size(); 81 | 82 | for (auto it = m_sorted_symbols.rbegin(); it != m_sorted_symbols.rend(); ++it) 83 | { 84 | const auto& symbols = it->second; 85 | 86 | if (symbols.empty()) 87 | { 88 | continue; 89 | } 90 | 91 | auto adjust = last_offset; 92 | 93 | // Use the last symbol as alias for all symbols (including itself) 94 | auto alias = symbols.back(); 95 | for (auto symbol : symbols) 96 | { 97 | if (int64_t size = adjust - symbol->Offset(); size >= 0) 98 | { 99 | symbol->SetSize(size); 100 | } 101 | 102 | last_offset = symbol->Offset(); 103 | m_symbol_refs.push_back(*symbol); 104 | symbol->SetAlias(alias); 105 | } 106 | } 107 | } 108 | 109 | void 110 | Section::Disassemble(IDisassembler& disassembler) 111 | { 112 | if (m_type != Type::kInstructions) 113 | { 114 | return; 115 | } 116 | 117 | // Should already be done, but anyway 118 | m_instruction_refs.clear(); 119 | m_instructions.clear(); 120 | 121 | std::unordered_set finished_offsets; 122 | 123 | for (const auto& [address, sym] : m_sorted_symbols) 124 | { 125 | if (uint64_t hint; m_disassembly_hint_queue.pop(hint) && m_sorted_symbols.contains(hint)) 126 | { 127 | DisassembleSymbol(m_sorted_symbols.find(hint)->second, disassembler); 128 | finished_offsets.insert(hint); 129 | } 130 | 131 | // Can be true if it was in the hint 132 | if (!finished_offsets.contains(address)) 133 | { 134 | DisassembleSymbol(sym, disassembler); 135 | finished_offsets.insert(address); 136 | } 137 | } 138 | 139 | for (const auto& insn : m_instructions) 140 | { 141 | if (auto file_line = m_line_lookup(insn->Offset()); file_line) 142 | { 143 | insn->SetSourceLocation(file_line->file, file_line->line); 144 | } 145 | 146 | if (auto rel_it = m_sorted_relocations.lower_bound(insn->Offset()); 147 | rel_it != m_sorted_relocations.end()) 148 | { 149 | auto reloc_dst = rel_it->first; 150 | 151 | auto& sym = rel_it->second->symbol.get(); 152 | auto insn_offset = insn->Offset(); 153 | 154 | if (reloc_dst >= insn_offset && reloc_dst < insn_offset + insn->Size()) 155 | { 156 | insn->SetRefersTo(sym.Section(), sym.Offset(), &sym); 157 | } 158 | } 159 | insn->Commit(); 160 | m_sorted_instructions[insn->Offset()] = insn.get(); 161 | } 162 | } 163 | 164 | void 165 | Section::DisassembleSymbol(std::vector symbols_at_address, IDisassembler& disassembler) 166 | { 167 | if (static_cast(symbols_at_address.front()->Offset()) < 0) 168 | { 169 | return; 170 | } 171 | 172 | auto sym = symbols_at_address.front(); 173 | 174 | auto size_before = m_instruction_refs.size(); 175 | disassembler.Disassemble( 176 | *this, sym, StartAddress() + sym->Offset(), sym->Data(), [this](auto insn) { 177 | m_instructions.push_back(std::move(insn)); 178 | 179 | m_instruction_refs.push_back(*m_instructions.back()); 180 | }); 181 | 182 | if (size_before != m_instruction_refs.size()) 183 | { 184 | sym->SetInstructions({m_instruction_refs.begin() + size_before, m_instruction_refs.end()}); 185 | } 186 | 187 | 188 | // The other symbols for the same address use the same instructions 189 | for (auto it = std::next(symbols_at_address.begin()); it != symbols_at_address.end(); ++it) 190 | { 191 | auto other_sym = *it; 192 | other_sym->SetInstructions( 193 | {m_instruction_refs.begin() + size_before, m_instruction_refs.end()}); 194 | 195 | other_sym->Commit(); 196 | } 197 | sym->Commit(); 198 | } 199 | 200 | std::span> 201 | Section::Instructions() const 202 | { 203 | return m_instruction_refs; 204 | } 205 | 206 | std::span> 207 | Section::Symbols() const 208 | { 209 | return m_symbol_refs; 210 | } 211 | 212 | bool 213 | Section::ContainsAddress(uint64_t address) const 214 | { 215 | auto start = StartAddress(); 216 | 217 | return address >= start && address < start + Size(); 218 | } 219 | 220 | IInstruction* 221 | Section::InstructionAt(uint64_t offset) const 222 | { 223 | if (auto it = m_sorted_instructions.find(offset); it != m_sorted_instructions.end()) 224 | { 225 | return it->second; 226 | } 227 | 228 | return nullptr; 229 | } 230 | 231 | void 232 | Section::FixupCrossReferences() 233 | { 234 | for (const auto& sym : m_symbols) 235 | { 236 | for (const auto& insn : sym->InstructionsStore()) 237 | { 238 | auto refers_to = insn.get().RefersTo(); 239 | auto referred_by = insn.get().ReferredBy(); 240 | 241 | if (refers_to) 242 | { 243 | sym.get()->AddRefersTo(*refers_to); 244 | } 245 | 246 | sym.get()->AddReferredBy(referred_by); 247 | 248 | insn.get().Commit(); 249 | } 250 | 251 | sym->Commit(); 252 | } 253 | } 254 | 255 | // Context: The UI thread 256 | void 257 | Section::DisassemblyHint(ISymbol& sym) const 258 | { 259 | m_disassembly_hint_queue.push(sym.Offset()); 260 | } 261 | -------------------------------------------------------------------------------- /test/unittest/test_lanes.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | #include "emilpro/jump_lanes.hh" 5 | #include "emilpro/mock/mock_instruction.hh" 6 | #include "test.h" 7 | 8 | #include 9 | #include 10 | 11 | using namespace emilpro; 12 | 13 | namespace 14 | { 15 | 16 | class Fixture 17 | { 18 | public: 19 | Fixture() 20 | { 21 | REQUIRE(instruction_refs.size() == instructions.size()); 22 | 23 | /* 24 | * 0: je 2 --. 25 | * 1: nop | 26 | * 2: nop <- 27 | * 3: nop 28 | * 4: je 10 -----. 29 | * 5: nop | 30 | * 6: je 9 --. | 31 | * 7: ,--------> nop | | 32 | * 8: | -> nop | | 33 | * 9: | | nop <-' | 34 | * 10: | | nop <-----' 35 | * 11: | ->|- -> nop 36 | * 12: | | | `- je 11 37 | * 13: | | `- je 8 38 | * 14: | `- je 11 39 | * 15: `---------- je 7 40 | */ 41 | for (auto i = 0u; i < instructions.size(); i++) 42 | { 43 | auto& cur = instructions[i]; 44 | 45 | expectations.push_back( 46 | NAMED_ALLOW_CALL(cur, Type()).RETURN(IInstruction::InstructionType::kOther)); 47 | expectations.push_back(NAMED_ALLOW_CALL(cur, RefersTo()).RETURN(std::nullopt)); 48 | expectations.push_back(NAMED_ALLOW_CALL(cur, ReferredBy()).RETURN(no_referers)); 49 | expectations.push_back(NAMED_ALLOW_CALL(cur, Offset()).RETURN(i)); 50 | } 51 | 52 | CreateReference(0, 2); 53 | CreateReference(4, 10); 54 | CreateReference(6, 9); 55 | 56 | CreateReference(12, 13); 57 | CreateReference(11, 13); 58 | 59 | // Backward 60 | CreateReference(12, 11); 61 | CreateReference(13, 8); 62 | CreateReference(14, 11); 63 | CreateReference(15, 7); 64 | } 65 | 66 | void CreateReference(size_t from_index, size_t to_index) 67 | { 68 | const auto& from = instructions[from_index]; 69 | const auto& to = instructions[to_index]; 70 | 71 | expectations.push_back(NAMED_ALLOW_CALL(from, RefersTo()) 72 | .RETURN(IInstruction::Referer {nullptr, to_index, nullptr})); 73 | expectations.push_back(NAMED_ALLOW_CALL(to, ReferredBy()) 74 | .RETURN(std::vector { 75 | IInstruction::Referer {nullptr, from_index, nullptr}})); 76 | expectations.push_back( 77 | NAMED_ALLOW_CALL(from, Type()).RETURN(IInstruction::InstructionType::kBranch)); 78 | } 79 | 80 | void PrintLanes(auto l) const 81 | { 82 | auto lane_to_char = [](JumpLanes::Type t) { 83 | switch (t) 84 | { 85 | case JumpLanes::Type::kNone: 86 | return ' '; 87 | case JumpLanes::Type::kStart: 88 | return 's'; 89 | case JumpLanes::Type::kStartEnd: 90 | return '+'; 91 | case JumpLanes::Type::kTraffic: 92 | return '.'; 93 | case JumpLanes::Type::kEnd: 94 | return 'e'; 95 | case JumpLanes::Type::kLongStart: 96 | return 'S'; 97 | case JumpLanes::Type::kLongEnd: 98 | return 'E'; 99 | } 100 | return '?'; 101 | }; 102 | 103 | int i = 0; 104 | fmt::print(" 0 1 2 3 0 1 2 3\n"); 105 | for (auto insn : l) 106 | { 107 | fmt::print("Instruction {:2d}: ", i); 108 | i++; 109 | for (auto backward : std::ranges::reverse_view(insn.backward_lanes)) 110 | { 111 | fmt::print(" {}", lane_to_char(backward)); 112 | } 113 | fmt::print(" xxx "); 114 | for (auto forward : insn.forward_lanes) 115 | { 116 | fmt::print(" {}", lane_to_char(forward)); 117 | } 118 | fmt::print("\n"); 119 | } 120 | } 121 | 122 | std::array instructions; 123 | std::vector> instruction_refs {instructions.begin(), 124 | instructions.end()}; 125 | JumpLanes lanes; 126 | 127 | private: 128 | std::vector no_referers; 129 | std::vector> expectations; 130 | }; 131 | 132 | } // namespace 133 | 134 | using T = JumpLanes::Type; 135 | 136 | TEST_CASE_FIXTURE(Fixture, "there are no lanes for instructions where no jumps pass") 137 | { 138 | REQUIRE(lanes.GetLanes().empty()); 139 | lanes.Calculate(16, instruction_refs); 140 | 141 | auto l = lanes.GetLanes(); 142 | REQUIRE(l.size() == instructions.size()); 143 | REQUIRE(std::ranges::equal(l[3].forward_lanes, 144 | std::array {T::kNone, T::kNone, T::kNone, T::kNone})); 145 | REQUIRE(std::ranges::equal(l[3].backward_lanes, 146 | std::array {T::kNone, T::kNone, T::kNone, T::kNone})); 147 | } 148 | 149 | TEST_CASE_FIXTURE(Fixture, "a single lane is used for a solitary jump") 150 | { 151 | lanes.Calculate(16, instruction_refs); 152 | 153 | auto l = lanes.GetLanes(); 154 | 155 | // 0..2 156 | REQUIRE(l[0].forward_lanes[0] == T::kStart); 157 | REQUIRE(l[1].forward_lanes[0] == T::kTraffic); 158 | REQUIRE(l[2].forward_lanes[0] == T::kEnd); 159 | } 160 | 161 | TEST_CASE_FIXTURE(Fixture, "a short maximum size is handled") 162 | { 163 | lanes.Calculate(3, instruction_refs); 164 | 165 | auto l = lanes.GetLanes(); 166 | 167 | // 0..2, short jump 168 | THEN("short jumps use lanes") 169 | { 170 | REQUIRE(l[0].forward_lanes[0] == T::kStart); 171 | REQUIRE(l[1].forward_lanes[0] == T::kTraffic); 172 | REQUIRE(l[2].forward_lanes[0] == T::kEnd); 173 | } 174 | 175 | THEN("long jumps use only start/end") 176 | { 177 | // 4..10, long jump 178 | REQUIRE(l[4].forward_lanes[0] == T::kLongStart); 179 | REQUIRE(l[5].forward_lanes[1] == T::kNone); 180 | REQUIRE(l[5].forward_lanes[2] == T::kNone); 181 | REQUIRE(l[5].forward_lanes[3] == T::kNone); 182 | REQUIRE(l[10].forward_lanes[0] == T::kLongEnd); 183 | } 184 | } 185 | 186 | TEST_CASE_FIXTURE(Fixture, "dual lanes are used for enclosing jumps") 187 | { 188 | lanes.Calculate(16, instruction_refs); 189 | 190 | auto l = lanes.GetLanes(); 191 | 192 | // 4..10 193 | REQUIRE(l[4].forward_lanes[1] == T::kStart); 194 | for (auto i = 5u; i < 10; i++) 195 | { 196 | REQUIRE(l[i].forward_lanes[1] == T::kTraffic); 197 | } 198 | REQUIRE(l[10].forward_lanes[1] == T::kEnd); 199 | 200 | // 6..8, enclosed 201 | REQUIRE(l[6].forward_lanes[0] == T::kStart); 202 | REQUIRE(l[7].forward_lanes[0] == T::kTraffic); 203 | REQUIRE(l[9].forward_lanes[0] == T::kEnd); 204 | } 205 | 206 | TEST_CASE_FIXTURE(Fixture, "backward lanes are also handled") 207 | { 208 | lanes.Calculate(16, instruction_refs); 209 | 210 | auto l = lanes.GetLanes(); 211 | REQUIRE(l[11].backward_lanes[0] == T::kEnd); 212 | REQUIRE(l[12].backward_lanes[0] == T::kStart); 213 | 214 | REQUIRE(l[8].backward_lanes[1] == T::kEnd); 215 | REQUIRE(l[13].backward_lanes[1] == T::kStart); 216 | 217 | REQUIRE(l[11].backward_lanes[2] == T::kEnd); 218 | REQUIRE(l[14].backward_lanes[2] == T::kStart); 219 | 220 | REQUIRE(l[7].backward_lanes[3] == T::kEnd); 221 | REQUIRE(l[15].backward_lanes[3] == T::kStart); 222 | } 223 | -------------------------------------------------------------------------------- /src/binary_parser/bfd_binary_parser.cc: -------------------------------------------------------------------------------- 1 | #include "bfd_binary_parser.hh" 2 | 3 | #include "section.hh" 4 | #include "symbol.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace emilpro; 22 | 23 | // ELF machine to the Machine enum mapping 24 | constexpr auto kMachineMap = std::array { 25 | std::pair {bfd_arch_mips, Machine::kMips}, 26 | std::pair {bfd_arch_powerpc, Machine::kPpc}, 27 | std::pair {bfd_arch_arm, Machine::kArm}, 28 | std::pair {bfd_arch_aarch64, Machine::kArm64}, 29 | std::pair {bfd_arch_riscv, Machine::kRiscV}, 30 | }; 31 | 32 | 33 | struct target_buffer 34 | { 35 | uint8_t* base; 36 | size_t size; 37 | }; 38 | 39 | // Helpers for libbfd 40 | /* Opening the file is a no-op. */ 41 | static void* 42 | mem_bfd_iovec_open(struct bfd* abfd, void* open_closure) 43 | { 44 | return open_closure; 45 | } 46 | 47 | /* Closing the file is just freeing the base/size pair on our side. */ 48 | 49 | static int 50 | mem_bfd_iovec_close(struct bfd* abfd, void* stream) 51 | { 52 | free(stream); 53 | return 1; 54 | } 55 | 56 | /* For reading the file, we just need to pass through to target_read_memory and 57 | fix up the arguments and return values. */ 58 | 59 | static file_ptr 60 | mem_bfd_iovec_pread(struct bfd* abfd, void* stream, void* buf, file_ptr nbytes, file_ptr offset) 61 | { 62 | struct target_buffer* buffer = (struct target_buffer*)stream; 63 | 64 | /* If this read will read all of the file, limit it to just the rest. */ 65 | if (offset + nbytes > (ssize_t)buffer->size) 66 | { 67 | nbytes = buffer->size - offset; 68 | } 69 | 70 | /* If there are no more bytes left, we've reached EOF. */ 71 | if (nbytes <= 0) 72 | { 73 | return 0; 74 | } 75 | 76 | memcpy(buf, buffer->base + offset, nbytes); 77 | 78 | return nbytes; 79 | } 80 | 81 | /* For statting the file, we only support the st_size attribute. */ 82 | static int 83 | mem_bfd_iovec_stat(struct bfd* abfd, void* stream, struct stat* sb) 84 | { 85 | struct target_buffer* buffer = (struct target_buffer*)stream; 86 | 87 | sb->st_size = buffer->size; 88 | return 0; 89 | } 90 | 91 | 92 | BfdBinaryParser::BfdBinaryParser(std::string_view path, std::optional machine_hint) 93 | : m_path(path) 94 | , m_machine_hint(machine_hint) 95 | { 96 | } 97 | 98 | BfdBinaryParser::~BfdBinaryParser() 99 | { 100 | if (m_bfd) 101 | { 102 | free(m_bfd_syms); 103 | free(m_dynamic_bfd_syms); 104 | free(m_synthetic_bfd_syms); 105 | bfd_close(m_bfd); 106 | m_bfd = nullptr; 107 | } 108 | } 109 | 110 | Machine 111 | BfdBinaryParser::GetMachine() const 112 | { 113 | return m_machine; 114 | } 115 | 116 | void 117 | BfdBinaryParser::ForAllSections(const std::function)>& on_section) 118 | { 119 | for (auto& [bfd_section, sec] : m_pending_sections) 120 | { 121 | on_section(std::move(sec)); 122 | } 123 | m_pending_sections.clear(); 124 | } 125 | 126 | 127 | std::optional 128 | BfdBinaryParser::LookupLine(bfd_section* section, bfd_symbol** symTbl, uint64_t offset) 129 | { 130 | const char* file_name; 131 | const char* function; 132 | unsigned int line_nr; 133 | 134 | // Use the section offset to lookup 135 | offset = offset - bfd_section_vma(section); 136 | 137 | if (bfd_find_nearest_line(m_bfd, section, symTbl, offset, &file_name, &function, &line_nr)) 138 | { 139 | if (!file_name) 140 | { 141 | return std::nullopt; 142 | } 143 | 144 | return Section::FileLine {file_name, line_nr}; 145 | } 146 | 147 | return std::nullopt; 148 | } 149 | 150 | 151 | bool 152 | BfdBinaryParser::Parse() 153 | { 154 | char** matching; 155 | unsigned int sz; 156 | struct target_buffer* buffer = (struct target_buffer*)malloc(sizeof(struct target_buffer)); 157 | 158 | // Mmap file 159 | struct stat filestat; 160 | 161 | auto fd = open(m_path.data(), O_RDONLY); 162 | assert(fd != -1); 163 | if (fstat(fd, &filestat) != 0) 164 | { 165 | perror("stat failed"); 166 | exit(1); 167 | } 168 | auto data = mmap(nullptr, filestat.st_size, PROT_READ, MAP_SHARED, fd, 0); 169 | if (data == MAP_FAILED) 170 | { 171 | perror("mmap failed"); 172 | exit(2); 173 | } 174 | 175 | buffer->base = (uint8_t*)data; 176 | buffer->size = filestat.st_size; 177 | m_rawData = (uint8_t*)data; 178 | m_rawDataSize = filestat.st_size; 179 | m_bfd = bfd_openr_iovec("", 180 | nullptr, 181 | mem_bfd_iovec_open, 182 | buffer, 183 | mem_bfd_iovec_pread, 184 | mem_bfd_iovec_close, 185 | mem_bfd_iovec_stat); 186 | 187 | if (!m_bfd) 188 | { 189 | return false; 190 | } 191 | if (!bfd_check_format_matches(m_bfd, bfd_object, &matching)) 192 | { 193 | bfd_close(m_bfd); 194 | m_bfd = nullptr; 195 | return false; 196 | } 197 | 198 | 199 | long symcount, dynsymcount, syntsymcount; 200 | bfd_symbol* syntheticSyms = nullptr; 201 | 202 | symcount = bfd_read_minisymbols(m_bfd, FALSE, (void**)&m_bfd_syms, &sz); 203 | dynsymcount = bfd_read_minisymbols(m_bfd, TRUE /* dynamic */, (void**)&m_dynamic_bfd_syms, &sz); 204 | syntsymcount = bfd_get_synthetic_symtab( 205 | m_bfd, symcount, m_bfd_syms, dynsymcount, m_dynamic_bfd_syms, &syntheticSyms); 206 | 207 | auto no_symbols = symcount <= 0 && dynsymcount <= 0 && syntsymcount <= 0; 208 | 209 | // Create sections 210 | for (auto section = m_bfd->sections; section != nullptr; section = section->next) 211 | { 212 | auto size = bfd_section_size(section); 213 | auto name = bfd_section_name(section); 214 | auto p = std::make_unique(size); 215 | 216 | auto type = ISection::Type::kOther; 217 | 218 | std::string flags; 219 | 220 | if (section->flags & SEC_ALLOC) 221 | { 222 | flags += "A"; 223 | } 224 | if (section->flags & SEC_LOAD) 225 | { 226 | flags += "L"; 227 | } 228 | if (section->flags & SEC_RELOC) 229 | { 230 | flags += "r"; 231 | } 232 | if (section->flags & SEC_READONLY) 233 | { 234 | flags += "Ro"; 235 | } 236 | if (section->flags & SEC_CODE) 237 | { 238 | flags += "C"; 239 | } 240 | if (section->flags & SEC_DATA) 241 | { 242 | flags += "D"; 243 | } 244 | if (section->flags & SEC_CONSTRUCTOR) 245 | { 246 | flags += "C"; 247 | } 248 | if (section->flags & SEC_KEEP) 249 | { 250 | flags += "K"; 251 | } 252 | 253 | 254 | std::unique_ptr
sec; 255 | if (bfd_get_section_contents(m_bfd, section, p.get(), 0, size)) 256 | { 257 | if (section->flags & SEC_CODE) 258 | { 259 | type = ISection::Type::kInstructions; 260 | } 261 | auto vma = bfd_section_vma(section); 262 | 263 | sec = std::make_unique
( 264 | name, 265 | std::span(reinterpret_cast(p.get()), size), 266 | vma, 267 | type, 268 | flags, 269 | [this, section](auto offset) { return LookupLine(section, m_bfd_syms, offset); }); 270 | 271 | if (no_symbols) 272 | { 273 | auto symbol = 274 | std::make_unique(*sec, vma, flags, fmt::format("Section {}", name)); 275 | sec->AddSymbol(std::move(symbol)); 276 | } 277 | } 278 | else 279 | { 280 | sec = std::make_unique
(name, 281 | std::span {}, 282 | bfd_section_vma(section), 283 | type, 284 | flags, 285 | [](auto) { return std::nullopt; }); 286 | } 287 | 288 | m_pending_sections[section] = std::move(sec); 289 | } 290 | 291 | if (auto undef_section = bfd_und_section_ptr) 292 | { 293 | m_pending_sections[undef_section] = 294 | std::make_unique
("*UNDEF*", 295 | std::span {}, 296 | 0, 297 | ISection::Type::kOther, 298 | "U", 299 | [](auto offset) { return std::nullopt; }); 300 | } 301 | 302 | 303 | HandleSymbols(symcount, m_bfd_syms, false); 304 | HandleSymbols(dynsymcount, m_dynamic_bfd_syms, true); 305 | if (syntheticSyms) 306 | { 307 | m_synthetic_bfd_syms = (bfd_symbol**)malloc(syntsymcount * sizeof(bfd_symbol*)); 308 | for (long i = 0; i < syntsymcount; i++) 309 | m_synthetic_bfd_syms[i] = &syntheticSyms[i]; 310 | HandleSymbols(syntsymcount, m_synthetic_bfd_syms, false); 311 | } 312 | 313 | for (auto& [section, sec] : m_pending_sections) 314 | { 315 | sec->FixupSymbolSizes(); 316 | } 317 | 318 | // The first pass has created symbols, now look for relocations 319 | for (auto section = m_bfd->sections; section != NULL; section = section->next) 320 | { 321 | HandleRelocations(section, m_bfd_syms); 322 | HandleRelocations(section, m_dynamic_bfd_syms); 323 | HandleRelocations(section, m_synthetic_bfd_syms); 324 | } 325 | 326 | auto arch = bfd_get_arch(m_bfd); 327 | auto machine = bfd_get_mach(m_bfd); 328 | 329 | if (arch == bfd_arch_i386) 330 | { 331 | if (machine & bfd_mach_i386_i8086) 332 | { 333 | m_machine = Machine::k8086; 334 | } 335 | else if (machine & bfd_mach_i386_i386) 336 | { 337 | m_machine = Machine::kI386; 338 | } 339 | else if (machine & bfd_mach_x86_64) 340 | { 341 | m_machine = Machine::kX86_64; 342 | } 343 | } 344 | else 345 | { 346 | auto it_machine = std::find_if( 347 | kMachineMap.begin(), kMachineMap.end(), [arch](auto& p) { return p.first == arch; }); 348 | if (it_machine != kMachineMap.end()) 349 | { 350 | m_machine = it_machine->second; 351 | } 352 | else if (m_machine_hint) 353 | { 354 | m_machine = *m_machine_hint; 355 | } 356 | } 357 | 358 | if (m_machine == Machine::kArm && m_arm_in_thumb_mode) 359 | { 360 | m_machine = Machine::kArmThumb; 361 | } 362 | 363 | return true; 364 | } 365 | 366 | void 367 | BfdBinaryParser::HandleSymbols(long symcount, bfd_symbol** syms, bool dynamic) 368 | { 369 | for (long i = 0; i < symcount; i++) 370 | { 371 | auto cur = syms[i]; 372 | 373 | if (!cur) 374 | { 375 | continue; 376 | } 377 | 378 | // An interesting symbol? 379 | if (cur->flags & (BSF_DEBUGGING | BSF_FILE | BSF_WARNING)) 380 | { 381 | continue; 382 | } 383 | 384 | auto sym_name = bfd_asymbol_name(cur); 385 | auto sym_addr = cur->value; 386 | auto section = bfd_asymbol_section(cur); 387 | 388 | if (bfd_get_arch(m_bfd) == bfd_arch_arm && sym_name) 389 | { 390 | // Skip ARM $a $d $t symbols 391 | if (strlen(sym_name) >= 2 && sym_name[0] == '$' && strchr("atd", sym_name[1]) && 392 | (sym_name[2] == '\0' || sym_name[2] == '.')) 393 | { 394 | if (sym_name[1] == 't') 395 | { 396 | m_arm_in_thumb_mode = true; 397 | } 398 | continue; 399 | } 400 | } 401 | 402 | auto sect_it = m_pending_sections.find(section); 403 | if (sect_it == m_pending_sections.end()) 404 | { 405 | continue; 406 | } 407 | 408 | std::string flags; 409 | 410 | if (dynamic) 411 | { 412 | flags += "D"; 413 | } 414 | 415 | if (cur->flags & BSF_LOCAL) 416 | { 417 | flags += "L"; 418 | } 419 | if (cur->flags & BSF_FUNCTION) 420 | { 421 | flags += "F"; 422 | } 423 | if (cur->flags & BSF_WEAK) 424 | { 425 | flags += "W"; 426 | } 427 | if (cur->flags & BSF_WEAK) 428 | { 429 | flags += "W"; 430 | } 431 | if (bfd_is_und_section(cur->section)) 432 | { 433 | flags += "U"; 434 | } 435 | 436 | auto symbol = std::make_unique(*sect_it->second, sym_addr, flags, sym_name); 437 | m_symbol_map[cur] = symbol.get(); 438 | sect_it->second->AddSymbol(std::move(symbol)); 439 | } 440 | } 441 | 442 | void 443 | BfdBinaryParser::HandleRelocations(asection* section, bfd_symbol** syms) 444 | { 445 | if (bfd_is_abs_section(section) || bfd_is_und_section(section) || bfd_is_com_section(section) || 446 | ((section->flags & SEC_RELOC) == 0)) 447 | { 448 | return; 449 | } 450 | if ((section->flags & SEC_ALLOC) == 0) 451 | { 452 | return; 453 | } 454 | 455 | auto relsize = bfd_get_reloc_upper_bound(m_bfd, section); 456 | if (relsize == 0) 457 | { 458 | return; 459 | } 460 | 461 | auto sect_it = m_pending_sections.find(section); 462 | if (sect_it == m_pending_sections.end()) 463 | { 464 | // A relocation for section we don't use 465 | return; 466 | } 467 | 468 | auto relpp = (arelent**)malloc(relsize); 469 | auto relcount = bfd_canonicalize_reloc(m_bfd, section, relpp, syms); 470 | 471 | if (relcount > 0) 472 | { 473 | auto sec = sect_it->second.get(); 474 | for (auto p = relpp; relcount && *p != NULL; p++, relcount--) 475 | { 476 | arelent* q = *p; 477 | 478 | auto sym_it = m_symbol_map.find(*q->sym_ptr_ptr); 479 | if (sym_it == m_symbol_map.end()) 480 | { 481 | continue; 482 | } 483 | auto sym = sym_it->second; 484 | sec->AddRelocation(q->address, *sym); 485 | sym->AddRelocation(*sec, q->address); 486 | } 487 | } 488 | free(relpp); 489 | } 490 | -------------------------------------------------------------------------------- /src/disassembly/capstone_disassembler.cc: -------------------------------------------------------------------------------- 1 | #include "capstone_disassembler.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace emilpro; 11 | 12 | constexpr auto kMachineMap = std::array { 13 | std::pair {Machine::k8086, cs_arch::CS_ARCH_X86}, 14 | std::pair {Machine::kI386, cs_arch::CS_ARCH_X86}, 15 | std::pair {Machine::kX86_64, cs_arch::CS_ARCH_X86}, 16 | std::pair {Machine::kArm, cs_arch::CS_ARCH_ARM}, 17 | std::pair {Machine::kArmThumb, cs_arch::CS_ARCH_ARM}, 18 | std::pair {Machine::kArm64, cs_arch::CS_ARCH_ARM64}, 19 | std::pair {Machine::kMips, cs_arch::CS_ARCH_MIPS}, 20 | std::pair {Machine::kPpc, cs_arch::CS_ARCH_PPC}, 21 | std::pair {Machine::kRiscV, cs_arch::CS_ARCH_RISCV}, 22 | }; 23 | static_assert(kMachineMap.size() == std::to_underlying(Machine::kUnknown)); 24 | 25 | namespace 26 | { 27 | 28 | class CapstoneInstruction : public IInstruction 29 | { 30 | public: 31 | CapstoneInstruction(csh handle, 32 | const ISection& section, 33 | const ISymbol* symbol, 34 | cs_arch arch, 35 | uint64_t offset, 36 | std::span data, 37 | const cs_insn* insn) 38 | : m_handle(handle) 39 | , m_section(section) 40 | , m_symbol(symbol) 41 | , m_data(data.subspan(0, std::min(static_cast(insn->size), data.size()))) 42 | , m_type(DetermineType(insn)) 43 | , m_encoding(fmt::format("{:8s} {}", insn->mnemonic, insn->op_str)) 44 | , m_offset(offset) 45 | { 46 | switch (arch) 47 | { 48 | case cs_arch::CS_ARCH_ARM: 49 | ProcessArm(insn); 50 | break; 51 | case cs_arch::CS_ARCH_X86: 52 | ProcessX86(insn); 53 | break; 54 | case cs_arch::CS_ARCH_ARM64: 55 | ProcessArm64(insn); 56 | break; 57 | case cs_arch::CS_ARCH_MIPS: 58 | ProcessMips(insn); 59 | break; 60 | case cs_arch::CS_ARCH_RISCV: 61 | ProcessRiscV(insn); 62 | break; 63 | default: 64 | break; 65 | } 66 | 67 | // Indirect registers 68 | for (auto i = 0u; i < insn->detail->regs_read_count; i++) 69 | { 70 | auto name = cs_reg_name(m_handle, insn->detail->regs_read[i]); 71 | 72 | if (name) 73 | { 74 | m_used_registers.emplace_back(name); 75 | } 76 | } 77 | for (auto i = 0u; i < insn->detail->regs_write_count; i++) 78 | { 79 | auto name = cs_reg_name(m_handle, insn->detail->regs_write[i]); 80 | 81 | if (name) 82 | { 83 | m_used_registers.emplace_back(name); 84 | } 85 | } 86 | } 87 | 88 | private: 89 | IInstruction::InstructionType DetermineType(const cs_insn* insn) const 90 | { 91 | for (auto i = 0u; i < insn->detail->groups_count; i++) 92 | { 93 | if (insn->detail->groups[i] == cs_group_type::CS_GRP_CALL) 94 | { 95 | return IInstruction::InstructionType::kCall; 96 | } 97 | else if (insn->detail->groups[i] == cs_group_type::CS_GRP_JUMP || 98 | insn->detail->groups[i] == cs_group_type::CS_GRP_BRANCH_RELATIVE) 99 | { 100 | return IInstruction::InstructionType::kBranch; 101 | } 102 | } 103 | 104 | return IInstruction::InstructionType::kOther; 105 | } 106 | 107 | void ProcessArm(const cs_insn* insn) 108 | { 109 | if (insn->id == arm_insn::ARM_INS_BL) 110 | { 111 | m_refers_to = IInstruction::Referer { 112 | nullptr, static_cast(insn->detail->arm.operands[0].imm), nullptr}; 113 | } 114 | else if (IsJump(insn) && insn->detail->arm.op_count > 0 && 115 | insn->detail->arm.operands[0].type == ARM_OP_IMM) 116 | { 117 | m_refers_to = IInstruction::Referer { 118 | &m_section, static_cast(insn->detail->arm.operands[0].imm), nullptr}; 119 | } 120 | 121 | for (auto i = 0u; i < insn->detail->arm.op_count; i++) 122 | { 123 | const auto& op = insn->detail->arm.operands[i]; 124 | if (op.type == ARM_OP_REG) 125 | { 126 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.reg)); 127 | } 128 | else if (op.type == ARM_OP_MEM) 129 | { 130 | if (op.mem.base != ARM_REG_INVALID) 131 | { 132 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.mem.base)); 133 | } 134 | if (op.mem.index != ARM_REG_INVALID) 135 | { 136 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.mem.index)); 137 | } 138 | } 139 | } 140 | } 141 | 142 | void ProcessArm64(const cs_insn* insn) 143 | { 144 | if (insn->id == arm64_insn::ARM64_INS_BL) 145 | { 146 | m_refers_to = IInstruction::Referer { 147 | nullptr, static_cast(insn->detail->arm64.operands[0].imm), nullptr}; 148 | } 149 | else if (IsJump(insn)) 150 | { 151 | for (auto i = 0u; i < insn->detail->arm64.op_count; i++) 152 | { 153 | const auto& op = insn->detail->arm64.operands[i]; 154 | if (op.type == ARM64_OP_IMM) 155 | { 156 | // Only consider the last immediate as an address (e.g., "tbnz w8, #0, #0xbe4") 157 | m_refers_to = 158 | IInstruction::Referer {&m_section, static_cast(op.imm), nullptr}; 159 | } 160 | } 161 | } 162 | 163 | for (auto i = 0u; i < insn->detail->arm64.op_count; i++) 164 | { 165 | const auto& op = insn->detail->arm64.operands[i]; 166 | if (op.type == ARM64_OP_REG) 167 | { 168 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.reg)); 169 | } 170 | else if (op.type == ARM64_OP_MEM) 171 | { 172 | if (op.mem.base != ARM64_REG_INVALID) 173 | { 174 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.mem.base)); 175 | } 176 | if (op.mem.index != ARM64_REG_INVALID) 177 | { 178 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.mem.index)); 179 | } 180 | } 181 | } 182 | } 183 | 184 | void ProcessX86(const cs_insn* insn) 185 | { 186 | auto upper_section_address = m_section.StartAddress() & 0xffffffff00000000ull; 187 | 188 | if (insn->id == x86_insn::X86_INS_CALL) 189 | { 190 | m_refers_to = IInstruction::Referer { 191 | nullptr, 192 | upper_section_address + static_cast(insn->detail->x86.operands[0].imm), 193 | nullptr}; 194 | } 195 | else if (IsJump(insn) && insn->detail->x86.op_count > 0 && 196 | insn->detail->x86.operands[0].type == X86_OP_IMM) 197 | { 198 | m_refers_to = IInstruction::Referer { 199 | &m_section, 200 | upper_section_address + static_cast(insn->detail->x86.operands[0].imm), 201 | nullptr}; 202 | } 203 | 204 | for (auto i = 0u; i < insn->detail->x86.op_count; i++) 205 | { 206 | const auto& op = insn->detail->x86.operands[i]; 207 | if (op.type == X86_OP_REG) 208 | { 209 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.reg)); 210 | } 211 | if (op.type == X86_OP_MEM) 212 | { 213 | if (op.mem.base != X86_REG_INVALID) 214 | { 215 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.mem.base)); 216 | } 217 | if (op.mem.index != X86_REG_INVALID) 218 | { 219 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.mem.index)); 220 | } 221 | } 222 | } 223 | } 224 | 225 | void ProcessMips(const cs_insn* insn) 226 | { 227 | if (insn->id == mips_insn::MIPS_INS_JAL || insn->id == mips_insn::MIPS_INS_BAL) 228 | { 229 | m_refers_to = IInstruction::Referer { 230 | nullptr, static_cast(insn->detail->mips.operands[0].imm), nullptr}; 231 | } 232 | else if (IsJump(insn) && insn->detail->mips.op_count > 0 && 233 | insn->detail->mips.operands[0].type == MIPS_OP_IMM) 234 | { 235 | m_refers_to = IInstruction::Referer { 236 | &m_section, static_cast(insn->detail->mips.operands[0].imm), nullptr}; 237 | } 238 | 239 | for (auto i = 0u; i < insn->detail->mips.op_count; i++) 240 | { 241 | const auto& op = insn->detail->mips.operands[i]; 242 | if (op.type == MIPS_OP_REG) 243 | { 244 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.reg)); 245 | } 246 | if (op.type == MIPS_OP_MEM) 247 | { 248 | if (op.mem.base != MIPS_REG_INVALID) 249 | { 250 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.mem.base)); 251 | } 252 | } 253 | } 254 | } 255 | 256 | void ProcessRiscV(const cs_insn* insn) 257 | { 258 | if (insn->id == riscv_insn::RISCV_INS_JAL) 259 | { 260 | m_refers_to = IInstruction::Referer { 261 | nullptr, 262 | m_section.StartAddress() + 263 | static_cast(insn->detail->riscv.operands[0].imm), 264 | nullptr}; 265 | } 266 | else if (IsJump(insn) && insn->detail->riscv.op_count > 0 && 267 | insn->detail->riscv.operands[0].type == RISCV_OP_IMM) 268 | { 269 | m_refers_to = IInstruction::Referer { 270 | &m_section, static_cast(insn->detail->riscv.operands[0].imm), nullptr}; 271 | } 272 | 273 | for (auto i = 0u; i < insn->detail->riscv.op_count; i++) 274 | { 275 | const auto& op = insn->detail->riscv.operands[i]; 276 | if (op.type == RISCV_OP_REG) 277 | { 278 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.reg)); 279 | } 280 | if (op.type == RISCV_OP_MEM) 281 | { 282 | if (op.mem.base != RISCV_REG_INVALID) 283 | { 284 | m_used_registers.emplace_back(cs_reg_name(m_handle, op.mem.base)); 285 | } 286 | } 287 | } 288 | } 289 | 290 | bool IsJump(const cs_insn* insn) const 291 | { 292 | return m_type == IInstruction::InstructionType::kBranch; 293 | } 294 | 295 | std::span Data() const final 296 | { 297 | return m_data; 298 | } 299 | 300 | InstructionType Type() const final 301 | { 302 | return m_type; 303 | } 304 | 305 | uint32_t Size() const final 306 | { 307 | return m_data.size(); 308 | } 309 | 310 | uint64_t Offset() const final 311 | { 312 | return m_offset; 313 | } 314 | 315 | std::string_view AsString() const final 316 | { 317 | return m_encoding; 318 | } 319 | 320 | std::span ReferredBy() const final 321 | { 322 | std::scoped_lock lock(m_mutex); 323 | 324 | return m_referred_by; 325 | } 326 | 327 | std::optional RefersTo() const final 328 | { 329 | std::scoped_lock lock(m_mutex); 330 | 331 | return m_refers_to; 332 | } 333 | 334 | void SetRefersTo(const ISection& section, uint64_t offset, const ISymbol* symbol) final 335 | { 336 | m_refers_to_store = IInstruction::Referer {§ion, offset, symbol}; 337 | } 338 | 339 | void AddReferredBy(const ISection& section, uint64_t offset, const ISymbol* symbol) final 340 | { 341 | m_referred_by_store.push_back({§ion, offset, symbol}); 342 | } 343 | 344 | void Commit() final 345 | { 346 | std::scoped_lock lock(m_mutex); 347 | 348 | if (!m_referred_by_store.empty()) 349 | { 350 | m_referred_by = m_referred_by_store; 351 | } 352 | if (m_refers_to_store) 353 | { 354 | m_refers_to = m_refers_to_store; 355 | } 356 | m_source_file = m_source_file_store; 357 | m_source_line = m_source_line_store; 358 | } 359 | 360 | std::span UsedRegisters() const final 361 | { 362 | return m_used_registers; 363 | } 364 | 365 | std::optional> GetSourceLocation() const final 366 | { 367 | std::scoped_lock lock(m_mutex); 368 | 369 | std::optional> out = std::nullopt; 370 | 371 | if (m_source_file && m_source_line) 372 | { 373 | out = {*m_source_file, *m_source_line}; 374 | } 375 | 376 | return out; 377 | } 378 | 379 | const ISection& Section() const final 380 | { 381 | return m_section; 382 | } 383 | 384 | const ISymbol* Symbol() const final 385 | { 386 | return m_symbol; 387 | } 388 | 389 | void SetSourceLocation(std::string_view file, uint32_t line) final 390 | { 391 | m_source_file_store = file; 392 | m_source_line_store = line; 393 | } 394 | 395 | 396 | csh m_handle; 397 | const ISection& m_section; 398 | const ISymbol* m_symbol; 399 | std::span m_data; 400 | const IInstruction::InstructionType m_type; 401 | std::optional m_refers_to; 402 | std::span m_referred_by; 403 | 404 | std::optional m_refers_to_store; 405 | std::vector m_referred_by_store; 406 | const std::string m_encoding; 407 | const uint64_t m_offset; 408 | 409 | std::optional m_source_file; 410 | std::optional m_source_line; 411 | std::optional m_source_file_store; 412 | std::optional m_source_line_store; 413 | 414 | std::vector m_used_registers; 415 | 416 | mutable std::mutex m_mutex; 417 | }; 418 | 419 | } // namespace 420 | 421 | 422 | CapstoneDisassembler::CapstoneDisassembler(cs_arch arch, cs_mode mode) 423 | : m_arch(arch) 424 | { 425 | cs_open(m_arch, mode, &m_handle); 426 | 427 | size_t option = CS_OPT_ON; 428 | 429 | cs_option(m_handle, CS_OPT_DETAIL, option); 430 | if (m_arch == cs_arch::CS_ARCH_X86) 431 | { 432 | option = CS_OPT_SYNTAX_ATT; 433 | cs_option(m_handle, CS_OPT_SYNTAX, option); 434 | } 435 | } 436 | 437 | CapstoneDisassembler::~CapstoneDisassembler() 438 | { 439 | cs_close(&m_handle); 440 | } 441 | 442 | void 443 | CapstoneDisassembler::Disassemble(const ISection& section, 444 | const ISymbol* symbol, 445 | uint64_t start_address, 446 | std::span data, 447 | std::function)> on_instruction) 448 | { 449 | auto insn = cs_malloc(m_handle); 450 | 451 | auto code = reinterpret_cast(data.data()); 452 | auto size = data.size(); 453 | auto address = start_address; 454 | auto cur_address = address; 455 | 456 | auto offset = 0; 457 | while (cs_disasm_iter(m_handle, &code, &size, &address, insn)) 458 | { 459 | on_instruction(std::make_unique( 460 | m_handle, section, symbol, m_arch, cur_address, data.subspan(offset), insn)); 461 | cur_address = address; 462 | offset += insn->size; 463 | } 464 | 465 | cs_free(insn, 1); 466 | } 467 | 468 | 469 | std::unique_ptr 470 | CapstoneDisassembler::Create(Machine machine) 471 | { 472 | auto it = std::ranges::find_if(kMachineMap, 473 | [machine](const auto& pair) { return pair.first == machine; }); 474 | 475 | if (it == kMachineMap.end()) 476 | { 477 | return nullptr; 478 | } 479 | unsigned int mode = CS_MODE_LITTLE_ENDIAN; 480 | 481 | switch (it->first) 482 | { 483 | case Machine::k8086: 484 | mode |= CS_MODE_16; 485 | break; 486 | case Machine::kI386: 487 | mode |= CS_MODE_32; 488 | break; 489 | case Machine::kX86_64: 490 | mode |= CS_MODE_64; 491 | break; 492 | case Machine::kArmThumb: 493 | mode |= CS_MODE_THUMB; 494 | break; 495 | case Machine::kRiscV: 496 | // TODO: Now hardcodes 32-bit / compressed mode 497 | mode |= CS_MODE_RISCV32 | CS_MODE_RISCVC; 498 | break; 499 | 500 | default: 501 | break; 502 | } 503 | 504 | auto p = new CapstoneDisassembler(it->second, static_cast(mode)); 505 | 506 | return std::unique_ptr(p); 507 | } 508 | 509 | std::unique_ptr 510 | IDisassembler::CreateFromArchitecture(Machine machine) 511 | { 512 | return CapstoneDisassembler::Create(machine); 513 | } 514 | -------------------------------------------------------------------------------- /qt/emilpro/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1427 10 | 896 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | EmilPRO 21 | 22 | 23 | true 24 | 25 | 26 | false 27 | 28 | 29 | 30 | 31 | 0 32 | 0 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 0 41 | 0 42 | 43 | 44 | 45 | Qt::Vertical 46 | 47 | 48 | 49 | 50 | 0 51 | 1 52 | 53 | 54 | 55 | Qt::Vertical 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Courier New 64 | 10 65 | false 66 | 67 | 68 | 69 | Qt::ClickFocus 70 | 71 | 72 | 73 | 74 | 75 | 76 | QLayout::SetDefaultConstraint 77 | 78 | 79 | 80 | 81 | true 82 | 83 | 84 | Qt::ClickFocus 85 | 86 | 87 | 0 88 | 89 | 90 | 91 | 92 | 0 93 | 0 94 | 95 | 96 | 97 | Symbols 98 | 99 | 100 | 101 | 0 102 | 103 | 104 | 0 105 | 106 | 107 | 0 108 | 109 | 110 | 0 111 | 112 | 113 | 114 | 115 | 116 | 0 117 | 0 118 | 119 | 120 | 121 | 122 | Courier New 123 | 12 124 | true 125 | 126 | 127 | 128 | 129 | 130 | 131 | QAbstractItemView::NoEditTriggers 132 | 133 | 134 | false 135 | 136 | 137 | QAbstractItemView::SelectRows 138 | 139 | 140 | false 141 | 142 | 143 | true 144 | 145 | 146 | false 147 | 148 | 149 | false 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 0 159 | 0 160 | 161 | 162 | 163 | Sections 164 | 165 | 166 | 167 | 0 168 | 169 | 170 | 0 171 | 172 | 173 | 0 174 | 175 | 176 | 0 177 | 178 | 179 | 180 | 181 | 182 | 0 183 | 0 184 | 185 | 186 | 187 | 188 | Courier New 189 | 12 190 | true 191 | 192 | 193 | 194 | Qt::ClickFocus 195 | 196 | 197 | 198 | 199 | 200 | QAbstractItemView::NoEditTriggers 201 | 202 | 203 | false 204 | 205 | 206 | QAbstractItemView::SelectRows 207 | 208 | 209 | false 210 | 211 | 212 | true 213 | 214 | 215 | false 216 | 217 | 218 | false 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 0 231 | 0 232 | 233 | 234 | 235 | Qt::ClickFocus 236 | 237 | 238 | 1 239 | 240 | 241 | 242 | 243 | 0 244 | 0 245 | 246 | 247 | 248 | Refers to 249 | 250 | 251 | 252 | 0 253 | 254 | 255 | 0 256 | 257 | 258 | 0 259 | 260 | 261 | 0 262 | 263 | 264 | 265 | 266 | 267 | 0 268 | 0 269 | 270 | 271 | 272 | 273 | Courier New 274 | 10 275 | 276 | 277 | 278 | Qt::ClickFocus 279 | 280 | 281 | QAbstractItemView::NoEditTriggers 282 | 283 | 284 | false 285 | 286 | 287 | QAbstractItemView::SelectRows 288 | 289 | 290 | false 291 | 292 | 293 | false 294 | 295 | 296 | false 297 | 298 | 299 | false 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 0 309 | 0 310 | 311 | 312 | 313 | Referenced by 314 | 315 | 316 | 317 | 0 318 | 319 | 320 | 0 321 | 322 | 323 | 0 324 | 325 | 326 | 0 327 | 328 | 329 | 330 | 331 | 332 | Courier New 333 | 10 334 | 335 | 336 | 337 | Qt::ClickFocus 338 | 339 | 340 | QAbstractItemView::NoEditTriggers 341 | 342 | 343 | false 344 | 345 | 346 | QAbstractItemView::SelectRows 347 | 348 | 349 | false 350 | 351 | 352 | false 353 | 354 | 355 | false 356 | 357 | 358 | false 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | Courier New 377 | 12 378 | false 379 | 380 | 381 | 382 | Qt::StrongFocus 383 | 384 | 385 | QAbstractItemView::NoEditTriggers 386 | 387 | 388 | false 389 | 390 | 391 | QAbstractItemView::SelectRows 392 | 393 | 394 | false 395 | 396 | 397 | false 398 | 399 | 400 | true 401 | 402 | 403 | false 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | Qt::Horizontal 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | Courier New 421 | 10 422 | 423 | 424 | 425 | 426 | 427 | 428 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 400 437 | 128 438 | 439 | 440 | 441 | 442 | Courier New 443 | 10 444 | false 445 | 446 | 447 | 448 | Qt::NoFocus 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | Address history 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 0 468 | 0 469 | 470 | 471 | 472 | 473 | 262 474 | 0 475 | 476 | 477 | 478 | 479 | Courier New 480 | 10 481 | 482 | 483 | 484 | Qt::ClickFocus 485 | 486 | 487 | QAbstractItemView::NoEditTriggers 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 0 502 | 0 503 | 1427 504 | 24 505 | 506 | 507 | 508 | 509 | &File 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | &Help 519 | 520 | 521 | 522 | 523 | 524 | Go 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | &Open 543 | 544 | 545 | Ctrl+O 546 | 547 | 548 | 549 | 550 | &Quit 551 | 552 | 553 | Ctrl+Q 554 | 555 | 556 | 557 | 558 | &Forward 559 | 560 | 561 | Ctrl+Right 562 | 563 | 564 | 565 | 566 | &Backward 567 | 568 | 569 | Ctrl+Left 570 | 571 | 572 | 573 | 574 | true 575 | 576 | 577 | true 578 | 579 | 580 | &Mangle names 581 | 582 | 583 | 584 | 585 | &Toggle data/instructions 586 | 587 | 588 | Ctrl+D 589 | 590 | 591 | 592 | 593 | true 594 | 595 | 596 | true 597 | 598 | 599 | &AT&&T syntax (x86) 600 | 601 | 602 | 603 | 604 | About 605 | 606 | 607 | 608 | 609 | &Reload file 610 | 611 | 612 | 613 | 614 | Focus &location filter bar 615 | 616 | 617 | Ctrl+L 618 | 619 | 620 | 621 | 622 | Toggle &reference tab 623 | 624 | 625 | Ctrl+K 626 | 627 | 628 | 629 | 630 | Focus references &to 631 | 632 | 633 | Ctrl+J 634 | 635 | 636 | 637 | 638 | Focus &address history 639 | 640 | 641 | Ctrl+A 642 | 643 | 644 | 645 | 646 | &Toggle symbols/sections 647 | 648 | 649 | Ctrl+T 650 | 651 | 652 | 653 | 654 | 655 | locationLineEdit 656 | symbolTableView 657 | instructionTableView 658 | 659 | 660 | 661 | 662 | -------------------------------------------------------------------------------- /qt/emilpro/mainwindow.cc: -------------------------------------------------------------------------------- 1 | #include "mainwindow.hh" 2 | 3 | #include "emilpro/i_disassembler.hh" 4 | #include "ui_mainwindow.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace 20 | { 21 | 22 | auto kSectionUndefinedColor = QBrush(Qt::lightGray); 23 | auto kSectionCodeColor = QBrush("lightgreen"); 24 | auto kSectionDataColor = QBrush("pink"); 25 | 26 | auto kSymbolUndefinedColor = kSectionUndefinedColor; 27 | auto kSymbolDataColor = kSectionDataColor; 28 | auto kSymbolDynamicDataColor = QBrush("salmon"); 29 | auto kSymbolDynamicColor = QBrush("lightgreen"); 30 | 31 | const char* 32 | LoadErrorToString(MainWindow::LoadError error) 33 | { 34 | using LE = MainWindow::LoadError; 35 | 36 | constexpr auto kErrorStrings = std::array { 37 | std::pair {LE::kFileNotFound, "File not found"}, 38 | std::pair {LE::kParseError, "Parse error"}, 39 | std::pair {LE::kUnknownArchitecture, "Unknown architecture"}, 40 | }; 41 | 42 | if (auto it = 43 | std::ranges::find_if(kErrorStrings, [error](auto& p) { return p.first == error; }); 44 | it != kErrorStrings.end()) 45 | { 46 | return it->second; 47 | } 48 | 49 | // Programming error 50 | assert(false); 51 | 52 | return ""; 53 | } 54 | 55 | } // namespace 56 | 57 | MainWindow::MainWindow(QWidget* parent) 58 | : QMainWindow(parent) 59 | , m_ui(new Ui::MainWindow) 60 | , m_forward_item_delegate(JumpLaneDelegate::Direction::kForward) 61 | , m_backward_item_delegate(JumpLaneDelegate::Direction::kBackward) 62 | { 63 | m_ui->setupUi(this); 64 | 65 | RestoreSettings(); 66 | 67 | SetupSectionView(); 68 | SetupSymbolView(); 69 | SetupInstructionView(); 70 | SetupReferencesView(); 71 | SetupAddressHistoryView(); 72 | 73 | // Set focus on the location line edit by default 74 | m_ui->locationLineEdit->setFocus(); 75 | 76 | m_highlighter = new Highlighter(m_ui->sourceTextEdit->document()); 77 | QTextEdit::ExtraSelection highlight; 78 | highlight.cursor = m_ui->sourceTextEdit->textCursor(); 79 | highlight.format.setProperty(QTextFormat::FullWidthSelection, true); 80 | highlight.format.setBackground(Qt::green); 81 | 82 | QList extras; 83 | extras << highlight; 84 | m_ui->sourceTextEdit->setExtraSelections(extras); 85 | 86 | m_ui->menuBar->setNativeMenuBar(false); 87 | } 88 | 89 | MainWindow::~MainWindow() 90 | { 91 | SaveSettings(); 92 | 93 | delete m_instruction_view_model; 94 | delete m_symbol_view_model; 95 | delete m_ui; 96 | } 97 | 98 | void 99 | MainWindow::TriggerOpenFile(const char* filename) 100 | { 101 | emit on_LoadFile(filename); 102 | } 103 | 104 | std::optional 105 | MainWindow::LoadFile(const std::string& filename, std::optional machine_hint) 106 | { 107 | auto parser = emilpro::IBinaryParser::FromFile(filename, machine_hint); 108 | if (!parser) 109 | { 110 | return LoadError::kParseError; 111 | } 112 | if (parser->GetMachine() == emilpro::Machine::kUnknown) 113 | { 114 | return LoadError::kUnknownArchitecture; 115 | } 116 | 117 | auto disassembler = emilpro::IDisassembler::CreateFromArchitecture(parser->GetMachine()); 118 | if (!disassembler) 119 | { 120 | return LoadError::kUnknownArchitecture; 121 | } 122 | 123 | 124 | m_database.ParseFile(std::move(parser), std::move(disassembler)); 125 | 126 | for (auto& section_ref : m_database.Sections()) 127 | { 128 | const auto& section = section_ref.get(); 129 | QList lst; 130 | 131 | QString addr = QString::fromStdString(fmt::format("0x{:x}", section.StartAddress())); 132 | QString size = QString::fromStdString(fmt::format("0x{:x}", section.Size())); 133 | QString flags = QString::fromStdString(section.Flags()); 134 | QString name = QString::fromStdString(section.Name()); 135 | 136 | lst.append(new QStandardItem(addr)); 137 | lst.append(new QStandardItem(size)); 138 | lst.append(new QStandardItem(flags)); 139 | lst.append(new QStandardItem(name)); 140 | 141 | m_section_view_model->appendRow(lst); 142 | auto last_row = m_section_view_model->rowCount() - 1; 143 | 144 | if (flags.contains("U")) 145 | { 146 | SetRowColor(m_section_view_model, last_row, kSectionUndefinedColor); 147 | } 148 | else if (flags.contains("C")) 149 | { 150 | SetRowColor(m_section_view_model, last_row, kSectionCodeColor); 151 | } 152 | else if (flags.contains("D")) 153 | { 154 | SetRowColor(m_section_view_model, last_row, kSectionDataColor); 155 | } 156 | 157 | m_ui->sectionTableView->setCurrentIndex(m_section_proxy_model->index(0, 0)); 158 | } 159 | 160 | m_visible_symbols = m_database.Symbols(); 161 | auto sym_index = 0; 162 | for (auto& sym_ref : m_visible_symbols) 163 | { 164 | const auto& sym = sym_ref.get(); 165 | 166 | QList lst; 167 | 168 | QString addr = QString::fromStdString( 169 | fmt::format("0x{:x}", sym.Section().StartAddress() + sym.Offset())); 170 | QString size = QString::fromStdString(fmt::format("0x{:x}", sym.Size())); 171 | QString flags = QString::fromStdString(sym.Flags()); 172 | QString section = QString::fromStdString(sym.Section().Name()); 173 | QString name = QString::fromStdString(sym.DemangledName()); 174 | 175 | 176 | // Store the symbol index in the address item (since they can be sorted arbitrarily) 177 | auto addr_item = new QStandardItem(addr); 178 | addr_item->setData(sym_index, Qt::UserRole + 1); 179 | sym_index++; 180 | 181 | lst.append(std::move(addr_item)); 182 | lst.append(new QStandardItem(size)); 183 | lst.append(new QStandardItem(flags)); 184 | lst.append(new QStandardItem(section)); 185 | lst.append(new QStandardItem(name)); 186 | 187 | m_symbol_view_model->appendRow(lst); 188 | auto last_row = m_symbol_view_model->rowCount() - 1; 189 | 190 | if (flags.contains("U")) 191 | { 192 | SetRowColor(m_symbol_view_model, last_row, kSymbolUndefinedColor); 193 | } 194 | else if (sym.Section().Flags().find("C") != std::string::npos) 195 | { 196 | if (flags.contains("D")) 197 | { 198 | SetRowColor(m_symbol_view_model, last_row, kSymbolDynamicColor); 199 | } 200 | } 201 | else if (sym.Section().Flags().find("D") != std::string::npos) 202 | { 203 | if (flags.contains("D")) 204 | { 205 | SetRowColor(m_symbol_view_model, last_row, kSymbolDynamicDataColor); 206 | } 207 | else 208 | { 209 | SetRowColor(m_symbol_view_model, last_row, kSymbolDataColor); 210 | } 211 | } 212 | 213 | m_ui->symbolTableView->setCurrentIndex(m_symbol_proxy_model->index(0, 0)); 214 | } 215 | 216 | m_symbol_proxy_model->sort(0, Qt::AscendingOrder); 217 | 218 | 219 | return std::nullopt; 220 | } 221 | 222 | void 223 | MainWindow::on_action_About_triggered(bool activated) 224 | { 225 | QString title = "About EmilPRO"; 226 | QString text = 227 | "
EmilPRO

" 228 | "
5 - \"Märsön\"


" 229 | "This application needs your help! Visit the webpage for more info and tasks to do!
" 230 | "
github.com/SimonKagstrom/emilpro
"; 233 | QMessageBox about; 234 | about.setWindowTitle(title); 235 | about.setText(text); 236 | about.setIconPixmap(QPixmap(":/images/logo.png")); 237 | about.exec(); 238 | } 239 | 240 | void 241 | MainWindow::OnHistoryIndexChanged() 242 | { 243 | auto entries = m_address_history.Entries(); 244 | if (entries.empty()) 245 | { 246 | // Nothing to do 247 | } 248 | 249 | const auto& entry = entries[m_address_history.CurrentIndex()]; 250 | auto lookup_result = 251 | m_database.LookupByAddress(entry.section, entry.section->StartAddress() + entry.offset); 252 | for (const auto& result : lookup_result) 253 | { 254 | if (auto sym_ref = result.symbol; sym_ref) 255 | { 256 | const auto& sym = sym_ref->get(); 257 | 258 | m_visible_instructions = sym.Instructions(); 259 | UpdateInstructionView(sym, sym.Offset(), entry.row); 260 | UpdateSymbolView(sym); 261 | } 262 | } 263 | 264 | UpdateHistoryView(); 265 | } 266 | 267 | void 268 | MainWindow::on_action_Backward_triggered(bool) 269 | { 270 | m_address_history.Backward(); 271 | 272 | OnHistoryIndexChanged(); 273 | } 274 | 275 | void 276 | MainWindow::on_action_Forward_triggered(bool) 277 | { 278 | m_address_history.Forward(); 279 | 280 | OnHistoryIndexChanged(); 281 | } 282 | 283 | void 284 | MainWindow::on_action_FocusLocationBar_triggered(bool activated) 285 | { 286 | m_ui->locationLineEdit->setFocus(); 287 | } 288 | 289 | void 290 | MainWindow::on_action_FocusAddressHistory_triggered(bool activated) 291 | { 292 | m_ui->addressHistoryListView->setFocus(); 293 | m_ui->addressHistoryListView->setCurrentIndex( 294 | m_address_history_view_model->index(m_address_history.CurrentIndex(), 0)); 295 | } 296 | 297 | void 298 | MainWindow::on_action_ToggleReferenceTab_triggered(bool) 299 | { 300 | auto next = !m_ui->referencesTabWidget->currentIndex(); 301 | 302 | m_ui->referencesTabWidget->setCurrentIndex(next); 303 | 304 | if (next == 0) 305 | { 306 | m_ui->refersToTableView->setFocus(); 307 | } 308 | else 309 | { 310 | m_ui->referredByTableView->setFocus(); 311 | } 312 | } 313 | 314 | void 315 | MainWindow::on_action_ToggleSymbolsSections_triggered(bool activated) 316 | { 317 | m_ui->sectionSymbolTabWidget->setCurrentIndex(!m_ui->sectionSymbolTabWidget->currentIndex()); 318 | } 319 | 320 | void 321 | MainWindow::on_action_Open_triggered(bool activated) 322 | { 323 | auto filename = QFileDialog::getOpenFileName(this, tr("Open binary")); 324 | 325 | if (filename.isEmpty()) 326 | { 327 | // Cancel 328 | return; 329 | } 330 | 331 | on_LoadFile(filename); 332 | } 333 | 334 | void 335 | MainWindow::on_LoadFile(const QString& filename) 336 | { 337 | auto err = LoadFile(filename.toStdString()); 338 | if (err) 339 | { 340 | if (err == LoadError::kUnknownArchitecture) 341 | { 342 | auto machine = SelectArchitecture(); 343 | 344 | if (machine) 345 | { 346 | err = LoadFile(filename.toStdString(), machine); 347 | } 348 | else 349 | { 350 | // Cancel, do nothing 351 | return; 352 | } 353 | } 354 | } 355 | 356 | // Still not OK? 357 | if (err) 358 | { 359 | QMessageBox::critical(this, 360 | "?LOAD ERROR", 361 | fmt::format("Cannot load file: {}", LoadErrorToString(*err)).c_str()); 362 | } 363 | } 364 | 365 | 366 | void 367 | MainWindow::on_action_Quit_triggered(bool activated) 368 | { 369 | QApplication::quit(); 370 | } 371 | 372 | void 373 | MainWindow::on_action_Refresh_triggered(bool activated) 374 | { 375 | on_action_Open_triggered(true); 376 | } 377 | 378 | void 379 | MainWindow::on_addressHistoryListView_activated(const QModelIndex& index) 380 | { 381 | auto entry_index = index.row(); 382 | if (entry_index < 0 || entry_index >= m_address_history.Entries().size()) 383 | { 384 | return; 385 | } 386 | 387 | m_address_history.SetIndex(entry_index); 388 | const auto& entry = m_address_history.Entries()[entry_index]; 389 | auto lookup_result = 390 | m_database.LookupByAddress(entry.section, entry.section->StartAddress() + entry.offset); 391 | for (const auto& result : lookup_result) 392 | { 393 | if (auto sym_ref = result.symbol; sym_ref) 394 | { 395 | const auto& sym = sym_ref->get(); 396 | 397 | m_visible_instructions = sym.Instructions(); 398 | UpdateInstructionView(sym, sym.Offset()); 399 | UpdateSymbolView(sym); 400 | } 401 | } 402 | } 403 | 404 | void 405 | MainWindow::on_insnCurrentChanged(const QModelIndex& index, const QModelIndex& previous) 406 | { 407 | auto row = index.row(); 408 | if (row < 0 || row >= m_visible_instructions.size()) 409 | { 410 | return; 411 | } 412 | 413 | const auto& insn = m_visible_instructions[row].get(); 414 | 415 | m_instruction_item_delegate.HighlightStrings(insn.UsedRegisters()); 416 | 417 | if (auto fl = insn.GetSourceLocation(); fl) 418 | { 419 | auto& source = LookupSourceFile(fl->first); 420 | auto line = fl->second == 0 ? 0 : fl->second - 1; 421 | 422 | if (source != m_current_source_file) 423 | { 424 | m_ui->sourceTextEdit->setText(source); 425 | } 426 | if (source != "") 427 | { 428 | QTextCursor cursor(m_ui->sourceTextEdit->document()->findBlockByLineNumber(line)); 429 | cursor.select(QTextCursor::LineUnderCursor); 430 | m_ui->sourceTextEdit->setTextCursor(cursor); 431 | } 432 | } 433 | 434 | UpdateRefersToView(insn); 435 | UpdateReferredByView(insn); 436 | 437 | // Force a repaint with the new register colors 438 | emit m_instruction_view_model->layoutChanged(); 439 | } 440 | 441 | void 442 | MainWindow::on_instructionTableView_activated(const QModelIndex& index) 443 | { 444 | on_instructionTableView_doubleClicked(index); 445 | } 446 | 447 | void 448 | MainWindow::on_instructionTableView_doubleClicked(const QModelIndex& index) 449 | { 450 | auto row = index.row(); 451 | if (row < 0 || row >= m_visible_instructions.size()) 452 | { 453 | return; 454 | } 455 | 456 | const auto& insn = m_visible_instructions[row].get(); 457 | 458 | auto refers_to = insn.RefersTo(); 459 | if (!refers_to) 460 | { 461 | return; 462 | } 463 | 464 | if (refers_to->symbol) 465 | { 466 | auto sym = refers_to->symbol; 467 | 468 | m_visible_instructions = sym->Instructions(); 469 | 470 | if (insn.Symbol()) 471 | { 472 | m_address_history.PushEntry(insn.Section(), 473 | insn.Offset() - insn.Section().StartAddress(), row); 474 | } 475 | m_address_history.PushEntry(sym->Section(), sym->Offset(), 0); 476 | 477 | UpdateInstructionView(*sym, sym->Offset()); 478 | UpdateSymbolView(*sym); 479 | UpdateHistoryView(); 480 | } 481 | else 482 | { 483 | auto lookup_result = m_database.LookupByAddress(&insn.Section(), refers_to->offset); 484 | 485 | for (const auto& result : lookup_result) 486 | { 487 | auto& section = result.section; 488 | 489 | if (auto sym_ref = result.symbol; sym_ref) 490 | { 491 | const auto& sym = sym_ref->get(); 492 | 493 | m_visible_instructions = sym.Instructions(); 494 | m_address_history.PushEntry(sym.Section(), result.offset, 0); 495 | 496 | UpdateInstructionView(sym, result.offset + section.StartAddress()); 497 | UpdateSymbolView(sym); 498 | UpdateHistoryView(); 499 | } 500 | } 501 | } 502 | } 503 | 504 | void 505 | MainWindow::on_locationLineEdit_textChanged(const QString& text) 506 | { 507 | auto is_address = false; 508 | auto address = text.toULongLong(&is_address, 16); 509 | 510 | auto lowest_visible = m_symbol_view_model->rowCount() - 1; 511 | 512 | // Hide all symbols which does not match the text / address 513 | for (auto i = 0u; i < m_symbol_view_model->rowCount(); i++) 514 | { 515 | // Lookup the index in the proxy (which is shown in the view) 516 | auto model_index = m_symbol_view_model->index(i, 0); 517 | auto proxy_index = m_symbol_proxy_model->mapFromSource(model_index); 518 | 519 | QString to_compare; 520 | const auto& sym = m_visible_symbols[i].get(); 521 | 522 | // Compare either addresses, or symbol names 523 | if (is_address) 524 | { 525 | to_compare = QString::number(sym.Section().StartAddress() + sym.Offset(), 16); 526 | } 527 | else 528 | { 529 | to_compare = QString(sym.DemangledName().c_str()); 530 | } 531 | 532 | // Ignore underscores and colons, unless explicitly given in the search string 533 | if (!text.contains("_")) 534 | { 535 | to_compare.remove("_"); 536 | } 537 | if (!text.contains(":")) 538 | { 539 | to_compare.remove(":"); 540 | } 541 | 542 | if (to_compare.contains(text, Qt::CaseInsensitive) || m_current_symbol == &sym) 543 | { 544 | m_ui->symbolTableView->showRow(proxy_index.row()); 545 | 546 | lowest_visible = std::min(lowest_visible, proxy_index.row()); 547 | } 548 | else 549 | { 550 | m_ui->symbolTableView->hideRow(proxy_index.row()); 551 | } 552 | } 553 | 554 | // ... and focus the first visible line 555 | m_ui->symbolTableView->setCurrentIndex(m_symbol_proxy_model->index(lowest_visible, 0)); 556 | } 557 | 558 | 559 | void 560 | MainWindow::on_locationLineEdit_returnPressed() 561 | { 562 | on_symbolTableView_activated(m_ui->symbolTableView->currentIndex()); 563 | } 564 | 565 | void 566 | MainWindow::on_refersToTableView_activated(const QModelIndex& index) 567 | { 568 | auto row = index.row(); 569 | 570 | if (row < 0 || row >= m_current_refers_to.size()) 571 | { 572 | return; 573 | } 574 | 575 | const auto& ref = m_current_refers_to[row]; 576 | if (ref.symbol) 577 | { 578 | UpdateSymbolView(*ref.symbol); 579 | UpdateInstructionView(*ref.symbol, ref.offset); 580 | m_ui->instructionTableView->setFocus(); 581 | } 582 | } 583 | 584 | void 585 | MainWindow::on_referredByTableView_activated(const QModelIndex& index) 586 | { 587 | auto row = index.row(); 588 | 589 | if (row < 0 || row >= m_current_referred_by.size()) 590 | { 591 | return; 592 | } 593 | 594 | const auto& ref = m_current_referred_by[row]; 595 | 596 | if (ref.symbol) 597 | { 598 | UpdateSymbolView(*ref.symbol); 599 | UpdateInstructionView(*ref.symbol, ref.offset); 600 | m_ui->instructionTableView->setFocus(); 601 | } 602 | } 603 | 604 | 605 | void 606 | MainWindow::on_sourceTextEdit_cursorPositionChanged() 607 | { 608 | } 609 | 610 | void 611 | MainWindow::on_symbolTableView_activated(const QModelIndex& index) 612 | { 613 | auto row = index.row(); 614 | 615 | if (row < 0 || row >= m_visible_symbols.size()) 616 | { 617 | return; 618 | } 619 | 620 | auto sym_index = index.model()->index(row, 0).data(Qt::UserRole + 1).toInt(); 621 | 622 | auto& sym = m_visible_symbols[sym_index].get(); 623 | 624 | sym.WaitForCommit(); 625 | 626 | m_visible_instructions = sym.Instructions(); 627 | m_address_history.PushEntry(sym.Section(), sym.Offset(), 0); 628 | 629 | UpdateInstructionView(sym, sym.Offset()); 630 | UpdateHistoryView(); 631 | m_ui->instructionTableView->setFocus(); 632 | } 633 | 634 | void 635 | MainWindow::UpdateRefersToView(const emilpro::ISymbol& symbol) 636 | { 637 | m_refers_to_view_model->removeRows(0, m_refers_to_view_model->rowCount()); 638 | 639 | auto symbol_refs = symbol.Alias()->RefersTo(); 640 | for (const auto& ref : symbol_refs) 641 | { 642 | auto section = ref.section; 643 | 644 | if (!section) 645 | { 646 | continue; 647 | } 648 | 649 | QList lst; 650 | lst.append(new QStandardItem( 651 | fmt::format("0x{:08x}", ref.offset + section->StartAddress()).c_str())); 652 | 653 | if (ref.symbol) 654 | { 655 | lst.append(new QStandardItem(QString::fromStdString(ref.symbol->DemangledName()))); 656 | } 657 | else 658 | { 659 | lst.append(new QStandardItem( 660 | QString::fromStdString(section->Name() + fmt::format("+0x{:x}", ref.offset)))); 661 | } 662 | m_refers_to_view_model->appendRow(lst); 663 | } 664 | 665 | m_current_refers_to = symbol_refs; 666 | } 667 | 668 | void 669 | MainWindow::UpdateRefersToView(const emilpro::IInstruction& insn) 670 | { 671 | m_refers_to_view_model->removeRows(0, m_refers_to_view_model->rowCount()); 672 | m_current_refers_to = {}; 673 | 674 | if (auto ref = insn.RefersTo(); ref) 675 | { 676 | auto section = ref->section; 677 | if (!section) 678 | { 679 | return; 680 | } 681 | 682 | QList lst; 683 | lst.append(new QStandardItem( 684 | fmt::format("0x{:08x}", ref->offset + section->StartAddress()).c_str())); 685 | 686 | if (ref->symbol) 687 | { 688 | lst.append(new QStandardItem(QString::fromStdString(ref->symbol->DemangledName()))); 689 | } 690 | else 691 | { 692 | lst.append(new QStandardItem( 693 | QString::fromStdString(section->Name() + fmt::format("+0x{:x}", ref->offset)))); 694 | } 695 | m_refers_to_view_model->appendRow(lst); 696 | 697 | // Store in a vector to keep the span valid, even though it's only one 698 | m_current_instruction_refers_to = {*ref}; 699 | m_current_refers_to = m_current_instruction_refers_to; 700 | } 701 | } 702 | 703 | 704 | void 705 | MainWindow::UpdateReferredByView(const emilpro::ISymbol& symbol) 706 | { 707 | m_referred_by_view_model->removeRows(0, m_referred_by_view_model->rowCount()); 708 | 709 | auto symbol_refs = symbol.Alias()->ReferredBy(); 710 | for (const auto& ref : symbol_refs) 711 | { 712 | auto section = ref.section; 713 | 714 | QList lst; 715 | lst.append(new QStandardItem(fmt::format("0x{:08x}", ref.offset).c_str())); 716 | 717 | if (ref.symbol) 718 | { 719 | lst.append(new QStandardItem(QString::fromStdString(ref.symbol->DemangledName()))); 720 | } 721 | else 722 | { 723 | lst.append(new QStandardItem( 724 | QString::fromStdString(section->Name() + fmt::format("+0x{:x}", ref.offset)))); 725 | } 726 | m_referred_by_view_model->appendRow(lst); 727 | } 728 | 729 | m_current_referred_by = symbol_refs; 730 | } 731 | 732 | void 733 | MainWindow::UpdateReferredByView(const emilpro::IInstruction& insn) 734 | { 735 | m_referred_by_view_model->removeRows(0, m_referred_by_view_model->rowCount()); 736 | 737 | auto insn_refs = insn.ReferredBy(); 738 | for (auto& ref : insn_refs) 739 | { 740 | auto section = ref.section; 741 | 742 | QList lst; 743 | lst.append(new QStandardItem(fmt::format("0x{:08x}", ref.offset).c_str())); 744 | 745 | if (ref.symbol) 746 | { 747 | lst.append(new QStandardItem(QString::fromStdString(ref.symbol->DemangledName()))); 748 | } 749 | else 750 | { 751 | lst.append(new QStandardItem( 752 | QString::fromStdString(section->Name() + fmt::format("+0x{:x}", ref.offset)))); 753 | } 754 | m_referred_by_view_model->appendRow(lst); 755 | } 756 | 757 | m_current_referred_by = insn_refs; 758 | } 759 | 760 | 761 | void 762 | MainWindow::on_symbolTableView_entered(const QModelIndex& index) 763 | { 764 | int row = index.row(); 765 | 766 | if (row < 0 || row >= m_visible_symbols.size()) 767 | { 768 | return; 769 | } 770 | 771 | 772 | // Create a new QModelIndex for column 0 of the same row 773 | auto sym_index = index.model()->index(row, 0).data(Qt::UserRole + 1).toInt(); 774 | 775 | // Assuming m_visible_symbols stores some kind of symbol objects and you need to retrieve it 776 | const auto& sym = m_visible_symbols[sym_index].get(); 777 | 778 | UpdateRefersToView(sym); 779 | UpdateReferredByView(sym); 780 | } 781 | 782 | void 783 | MainWindow::on_symbolTimerTriggered() 784 | { 785 | } 786 | 787 | void 788 | MainWindow::RestoreSettings() 789 | { 790 | QSettings settings("ska", "emilpro"); 791 | 792 | settings.beginGroup("MainWindow"); 793 | if (const auto geometry = settings.value("geometry", QByteArray()).toByteArray(); 794 | !geometry.isEmpty()) 795 | { 796 | restoreGeometry(geometry); 797 | } 798 | settings.endGroup(); 799 | 800 | settings.beginGroup("Splitters"); 801 | if (const auto state = 802 | settings.value("symbol_instruction_splitter_size", QByteArray()).toByteArray(); 803 | !state.isEmpty()) 804 | { 805 | m_ui->symbolInstructionSplitter->restoreState(state); 806 | } 807 | if (const auto state = 808 | settings.value("instruction_source_splitter_size", QByteArray()).toByteArray(); 809 | !state.isEmpty()) 810 | { 811 | m_ui->instructionSourceSplitter->restoreState(state); 812 | } 813 | settings.endGroup(); 814 | } 815 | 816 | void 817 | MainWindow::SaveSettings() 818 | { 819 | QSettings settings("ska", "emilpro"); 820 | 821 | settings.beginGroup("MainWindow"); 822 | settings.setValue("geometry", saveGeometry()); 823 | settings.endGroup(); 824 | 825 | settings.beginGroup("Splitters"); 826 | settings.setValue("symbol_instruction_splitter_size", 827 | m_ui->symbolInstructionSplitter->saveState()); 828 | settings.setValue("instruction_source_splitter_size", 829 | m_ui->instructionSourceSplitter->saveState()); 830 | settings.endGroup(); 831 | } 832 | 833 | void 834 | MainWindow::SetupAddressHistoryView() 835 | { 836 | m_address_history_view_model = new QStandardItemModel(0, 1, this); 837 | 838 | m_ui->addressHistoryListView->setModel(m_address_history_view_model); 839 | } 840 | 841 | void 842 | MainWindow::SetupInstructionLabels() 843 | { 844 | QStringList labels; 845 | 846 | labels << "Address" 847 | << "B" 848 | << "Instruction" 849 | << "F" 850 | << "Raw" 851 | << "Target"; 852 | 853 | m_instruction_view_model->setHorizontalHeaderLabels(labels); 854 | } 855 | 856 | void 857 | MainWindow::SetupInstructionView() 858 | { 859 | m_instruction_view_model = new QStandardItemModel(0, 6, this); 860 | 861 | m_instruction_view_model->setHorizontalHeaderItem(0, new QStandardItem(QString("Address"))); 862 | m_instruction_view_model->setHorizontalHeaderItem(1, new QStandardItem(QString("B"))); 863 | m_instruction_view_model->setHorizontalHeaderItem(2, new QStandardItem(QString("Instruction"))); 864 | m_instruction_view_model->setHorizontalHeaderItem(3, new QStandardItem(QString("F"))); 865 | m_instruction_view_model->setHorizontalHeaderItem(4, new QStandardItem(QString("Raw"))); 866 | m_instruction_view_model->setHorizontalHeaderItem(5, new QStandardItem(QString("Target"))); 867 | 868 | m_ui->instructionTableView->setItemDelegateForColumn(1, &m_backward_item_delegate); 869 | m_ui->instructionTableView->setItemDelegateForColumn(2, &m_instruction_item_delegate); 870 | m_ui->instructionTableView->setItemDelegateForColumn(3, &m_forward_item_delegate); 871 | 872 | m_ui->instructionTableView->setModel(m_instruction_view_model); 873 | m_ui->instructionTableView->horizontalHeader()->setStretchLastSection(true); 874 | m_ui->instructionTableView->resizeColumnsToContents(); 875 | 876 | m_ui->instructionTableView->setColumnWidth(0, 120); 877 | m_ui->instructionTableView->setColumnWidth(1, 80); 878 | m_ui->instructionTableView->setColumnWidth(2, 300); 879 | m_ui->instructionTableView->setColumnWidth(3, 80); 880 | m_ui->instructionTableView->setColumnWidth(4, 200); 881 | 882 | connect(m_ui->instructionTableView->selectionModel(), 883 | SIGNAL(currentChanged(QModelIndex, QModelIndex)), 884 | this, 885 | SLOT(on_insnCurrentChanged(QModelIndex, QModelIndex))); 886 | 887 | SetupInstructionLabels(); 888 | 889 | m_ui->instructionTableView->installEventFilter(this); 890 | } 891 | 892 | void 893 | MainWindow::SetupReferencesView() 894 | { 895 | m_refers_to_view_model = new QStandardItemModel(0, 2, this); 896 | m_referred_by_view_model = new QStandardItemModel(0, 2, this); 897 | 898 | m_ui->refersToTableView->setModel(m_refers_to_view_model); 899 | m_ui->refersToTableView->setColumnWidth(0, 80); 900 | m_ui->refersToTableView->horizontalHeader()->setStretchLastSection(true); 901 | 902 | m_ui->referredByTableView->setModel(m_referred_by_view_model); 903 | m_ui->referredByTableView->setColumnWidth(0, 80); 904 | m_ui->referredByTableView->horizontalHeader()->setStretchLastSection(true); 905 | } 906 | 907 | void 908 | MainWindow::SetupSectionView() 909 | { 910 | m_section_view_model = new QStandardItemModel(0, 4, this); 911 | m_section_proxy_model = std::make_unique(this); 912 | m_section_proxy_model->setSourceModel(m_section_view_model); 913 | m_section_proxy_model->setSortCaseSensitivity(Qt::CaseInsensitive); 914 | 915 | m_section_view_model->setHorizontalHeaderItem(0, new QStandardItem(QString("Address"))); 916 | m_section_view_model->setHorizontalHeaderItem(1, new QStandardItem(QString("Size"))); 917 | m_section_view_model->setHorizontalHeaderItem(2, new QStandardItem(QString("Flags"))); 918 | m_section_view_model->setHorizontalHeaderItem(3, new QStandardItem(QString("Name"))); 919 | 920 | m_ui->sectionTableView->horizontalHeader()->setStretchLastSection(true); 921 | m_ui->sectionTableView->horizontalHeader()->setSortIndicatorShown(true); 922 | 923 | m_ui->sectionTableView->resizeColumnsToContents(); 924 | m_ui->sectionTableView->setColumnWidth(0, 120); 925 | m_ui->sectionTableView->setColumnWidth(1, 80); 926 | m_ui->sectionTableView->setColumnWidth(2, 80); 927 | m_ui->sectionTableView->setSelectionMode(QAbstractItemView::SingleSelection); 928 | 929 | m_ui->sectionTableView->setModel(m_section_proxy_model.get()); 930 | 931 | connect(m_ui->sectionTableView->horizontalHeader(), 932 | &QHeaderView::sectionClicked, 933 | [this](int column) { 934 | auto current_order = m_section_proxy_model->sortOrder(); 935 | int current_sort_column = m_section_proxy_model->sortColumn(); 936 | 937 | if (column == 2) 938 | { 939 | // Don't allow sorting by flags 940 | return; 941 | } 942 | 943 | if (column == current_sort_column) 944 | { 945 | // Toggle the sort order if the same column is clicked 946 | current_order = (current_order == Qt::AscendingOrder) ? Qt::DescendingOrder 947 | : Qt::AscendingOrder; 948 | } 949 | else 950 | { 951 | // Default to ascending order if a different column is clicked 952 | current_order = Qt::AscendingOrder; 953 | } 954 | 955 | // Apply the sorting 956 | m_section_proxy_model->sort(column, current_order); 957 | }); 958 | } 959 | 960 | void 961 | MainWindow::SetupSymbolView() 962 | { 963 | m_symbol_view_model = new QStandardItemModel(0, 4, this); 964 | m_symbol_proxy_model = std::make_unique(this); 965 | m_symbol_proxy_model->setSourceModel(m_symbol_view_model); 966 | m_symbol_proxy_model->setSortCaseSensitivity(Qt::CaseInsensitive); 967 | 968 | 969 | m_symbol_view_model->setHorizontalHeaderItem(0, new QStandardItem(QString("Address"))); 970 | m_symbol_view_model->setHorizontalHeaderItem(1, new QStandardItem(QString("Size"))); 971 | m_symbol_view_model->setHorizontalHeaderItem(2, new QStandardItem(QString("Flags"))); 972 | m_symbol_view_model->setHorizontalHeaderItem(3, new QStandardItem(QString("Section"))); 973 | m_symbol_view_model->setHorizontalHeaderItem(4, new QStandardItem(QString("Symbol name"))); 974 | 975 | m_ui->symbolTableView->horizontalHeader()->setStretchLastSection(true); 976 | m_ui->symbolTableView->horizontalHeader()->setSortIndicatorShown(true); 977 | 978 | m_ui->symbolTableView->resizeColumnsToContents(); 979 | m_ui->symbolTableView->setColumnWidth(0, 120); 980 | m_ui->symbolTableView->setColumnWidth(1, 80); 981 | m_ui->symbolTableView->setColumnWidth(3, 160); 982 | m_ui->symbolTableView->setSelectionMode(QAbstractItemView::SingleSelection); 983 | 984 | // Install an event filter to have the Enter key behave like activate 985 | m_ui->symbolTableView->installEventFilter(this); 986 | m_ui->symbolTableView->setModel(m_symbol_proxy_model.get()); 987 | 988 | connect(m_ui->symbolTableView->horizontalHeader(), 989 | &QHeaderView::sectionClicked, 990 | [this](int column) { 991 | Qt::SortOrder current_order = m_symbol_proxy_model->sortOrder(); 992 | int current_sort_column = m_symbol_proxy_model->sortColumn(); 993 | 994 | if (column == 2) 995 | { 996 | // Don't allow sorting by flags 997 | return; 998 | } 999 | 1000 | if (column == current_sort_column) 1001 | { 1002 | // Toggle the sort order if the same column is clicked 1003 | current_order = (current_order == Qt::AscendingOrder) ? Qt::DescendingOrder 1004 | : Qt::AscendingOrder; 1005 | } 1006 | else 1007 | { 1008 | // Default to ascending order if a different column is clicked 1009 | current_order = Qt::AscendingOrder; 1010 | } 1011 | 1012 | // Apply the sorting 1013 | m_symbol_proxy_model->sort(column, current_order); 1014 | }); 1015 | 1016 | connect(m_ui->symbolTableView->selectionModel(), 1017 | SIGNAL(currentChanged(QModelIndex, QModelIndex)), 1018 | this, 1019 | SLOT(on_symbolTableView_entered(QModelIndex))); 1020 | } 1021 | 1022 | 1023 | void 1024 | MainWindow::UpdateSymbolView(const emilpro::ISymbol& symbol) 1025 | { 1026 | for (auto row = 0u; row < m_symbol_proxy_model->rowCount(); row++) 1027 | { 1028 | auto sym_index = m_symbol_proxy_model->index(row, 0).data(Qt::UserRole + 1).toInt(); 1029 | 1030 | if (&m_visible_symbols[sym_index].get() == &symbol) 1031 | { 1032 | m_ui->symbolTableView->showRow(row); 1033 | m_ui->symbolTableView->selectRow(row); 1034 | m_ui->symbolTableView->scrollTo(m_symbol_proxy_model->index(row, 0)); 1035 | return; 1036 | } 1037 | } 1038 | } 1039 | 1040 | void 1041 | MainWindow::UpdateInstructionView(const emilpro::ISymbol& symbol, uint64_t offset, uint32_t row) 1042 | { 1043 | m_instruction_view_model->removeRows(0, m_instruction_view_model->rowCount()); 1044 | auto selected_row = 0; 1045 | 1046 | m_forward_item_delegate.Update(64, symbol.Instructions()); 1047 | m_backward_item_delegate.Update(64, symbol.Instructions()); 1048 | 1049 | for (auto& ref : symbol.Instructions()) 1050 | { 1051 | const auto& ri = ref.get(); 1052 | const auto& section = ri.Section(); 1053 | 1054 | auto refers_to = ri.RefersTo(); 1055 | 1056 | 1057 | // Workaround an UBSAN issue with fmt::format, when fmt::join is used 1058 | std::string encoding; 1059 | 1060 | for (auto x : ri.Data()) 1061 | { 1062 | encoding += fmt::format("{:02x} ", x); 1063 | } 1064 | 1065 | QList lst; 1066 | lst.append(new QStandardItem(fmt::format("0x{:08x}", ri.Offset()).c_str())); 1067 | lst.append(nullptr); // Backward branch 1068 | lst.append(new QStandardItem(std::string(ri.AsString()).c_str())); 1069 | lst.append(nullptr); // Forward branch 1070 | lst.append(new QStandardItem(encoding.c_str())); // Encoding 1071 | if (refers_to) 1072 | { 1073 | if (refers_to->symbol) 1074 | { 1075 | lst.append( 1076 | new QStandardItem(QString::fromStdString(refers_to->symbol->DemangledName()))); 1077 | } 1078 | else 1079 | { 1080 | lst.append(new QStandardItem(fmt::format("0x{:08x}", refers_to->offset).c_str())); 1081 | } 1082 | } 1083 | else 1084 | { 1085 | lst.append(nullptr); 1086 | } 1087 | 1088 | 1089 | auto cur_row = m_instruction_view_model->rowCount(); 1090 | if (ri.Offset() == offset) 1091 | { 1092 | selected_row = cur_row; 1093 | } 1094 | else if (cur_row == row) 1095 | { 1096 | selected_row = cur_row; 1097 | } 1098 | 1099 | m_instruction_view_model->appendRow(lst); 1100 | } 1101 | 1102 | m_ui->instructionTableView->selectRow(selected_row); 1103 | m_ui->instructionTableView->scrollTo(m_instruction_view_model->index(selected_row, 0)); 1104 | } 1105 | 1106 | void 1107 | MainWindow::UpdateHistoryView() 1108 | { 1109 | m_address_history_view_model->removeRows(0, m_address_history_view_model->rowCount()); 1110 | 1111 | for (const auto& entry : m_address_history.Entries()) 1112 | { 1113 | auto lookup_result = 1114 | m_database.LookupByAddress(entry.section, entry.section->StartAddress() + entry.offset); 1115 | 1116 | QString str; 1117 | 1118 | if (lookup_result.size() > 0 && lookup_result[0].symbol) 1119 | { 1120 | const auto& sym = lookup_result[0].symbol->get(); 1121 | str = QString::fromStdString(fmt::format( 1122 | "0x{:x} ({})", entry.section->StartAddress() + entry.offset, sym.DemangledName())); 1123 | } 1124 | else 1125 | { 1126 | str = QString::fromStdString( 1127 | fmt::format("0x{:x}", entry.section->StartAddress() + entry.offset)); 1128 | } 1129 | 1130 | m_address_history_view_model->appendRow(new QStandardItem(str)); 1131 | } 1132 | 1133 | auto idx = m_address_history_view_model->index(m_address_history.CurrentIndex(), 0); 1134 | m_ui->addressHistoryListView->setCurrentIndex(idx); 1135 | } 1136 | 1137 | const QString& 1138 | MainWindow::LookupSourceFile(std::string_view path) 1139 | { 1140 | auto it = m_source_file_map.find(std::string()); 1141 | auto sp = std::string(path); 1142 | 1143 | if (it == m_source_file_map.end()) 1144 | { 1145 | QFile f(sp.c_str()); 1146 | 1147 | if (f.open(QFile::ReadOnly | QFile::Text)) 1148 | { 1149 | QTextStream in(&f); 1150 | 1151 | m_source_file_map.emplace(sp, in.readAll()); 1152 | } 1153 | else 1154 | { 1155 | m_source_file_map.emplace(sp, ""); 1156 | } 1157 | } 1158 | 1159 | return m_source_file_map[sp]; 1160 | } 1161 | 1162 | void 1163 | MainWindow::SetRowColor(QAbstractItemModel* model, 1164 | int row, 1165 | const QBrush& color, 1166 | const QModelIndex& parent) 1167 | { 1168 | assert(model); 1169 | assert(row >= 0 && row < model->rowCount(parent)); 1170 | 1171 | const int colCount = model->columnCount(parent); 1172 | for (int j = 0; j < colCount; ++j) 1173 | { 1174 | model->setData(model->index(row, j, parent), color, Qt::BackgroundRole); 1175 | } 1176 | } 1177 | 1178 | std::optional 1179 | MainWindow::SelectArchitecture() 1180 | { 1181 | QStringList architectures; 1182 | 1183 | for (auto i = 0; i < static_cast(emilpro::Machine::kUnknown); i++) 1184 | { 1185 | architectures << QString::fromStdString(MachineToString(static_cast(i))); 1186 | } 1187 | 1188 | auto ok = false; 1189 | auto selected = QInputDialog::getItem( 1190 | this, "Select Architecture", "Architecture:", architectures, 0, false, &ok); 1191 | 1192 | std::optional machine; 1193 | if (ok && !selected.isEmpty()) 1194 | { 1195 | machine = emilpro::MachineFromString(selected.toStdString()); 1196 | } 1197 | 1198 | return machine; 1199 | } 1200 | 1201 | bool 1202 | MainWindow::eventFilter(QObject* watched, QEvent* event) 1203 | { 1204 | // (Thanks to copilot for much of this code!) 1205 | if (event->type() == QEvent::KeyPress) 1206 | { 1207 | QKeyEvent* keyEvent = static_cast(event); 1208 | if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) 1209 | { 1210 | auto p = watched == m_ui->symbolTableView ? m_ui->symbolTableView 1211 | : m_ui->instructionTableView; 1212 | 1213 | // Get the current index 1214 | QModelIndex currentIndex = p->currentIndex(); 1215 | if (currentIndex.isValid()) 1216 | { 1217 | // Emit the activated signal with the current index 1218 | emit p->activated(currentIndex); 1219 | return true; // Indicate that the event was handled 1220 | } 1221 | } 1222 | } 1223 | 1224 | return QMainWindow::eventFilter(watched, event); // Pass the event on to the base class 1225 | } 1226 | --------------------------------------------------------------------------------