├── .gitignore ├── CMakeLists.txt ├── README.md ├── cmake ├── compilation.cmake ├── deps.cmake ├── mac.cmake ├── platform.cmake └── target.cmake └── src ├── BinaryObject.cpp ├── BinaryObject.h ├── CMakeLists.txt ├── Config.cpp ├── Config.h ├── CpuType.h ├── FileType.h ├── Reader.cpp ├── Reader.h ├── Section.cpp ├── Section.h ├── SectionType.h ├── SymbolTable.cpp ├── SymbolTable.h ├── Util.cpp ├── Util.h ├── Version.h ├── asm ├── Asm.h ├── AsmX86.cpp ├── AsmX86.h ├── Disassembler.cpp └── Disassembler.h ├── formats ├── Format.cpp ├── Format.h ├── FormatType.h ├── MachO.cpp └── MachO.h ├── main.cpp ├── panes ├── ArchPane.cpp ├── ArchPane.h ├── DisassemblyPane.cpp ├── DisassemblyPane.h ├── GenericPane.cpp ├── GenericPane.h ├── Pane.h ├── ProgramPane.cpp ├── ProgramPane.h ├── StringsPane.cpp ├── StringsPane.h ├── SymbolsPane.cpp └── SymbolsPane.h └── widgets ├── BinaryWidget.cpp ├── BinaryWidget.h ├── ConversionHelper.cpp ├── ConversionHelper.h ├── DisassemblerDialog.cpp ├── DisassemblerDialog.h ├── LineEdit.cpp ├── LineEdit.h ├── MachineCodeWidget.cpp ├── MachineCodeWidget.h ├── MainWindow.cpp ├── MainWindow.h ├── PreferencesDialog.cpp ├── PreferencesDialog.h ├── TreeWidget.cpp └── TreeWidget.h /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .#* 3 | \#* 4 | build* 5 | *.slo 6 | *.lo 7 | *.o 8 | *.so 9 | *.dylib 10 | *.lai 11 | *.la 12 | *.a 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) 2 | PROJECT(bmod) 3 | 4 | SET(CMAKE_COLOR_MAKEFILE ON) 5 | SET(CMAKE_VERBOSE_MAKEFILE OFF) 6 | SET(CMAKE_INCLUDE_CURRENT_DIR TRUE) 7 | SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS TRUE) 8 | 9 | SET(CMAKE_MODULE_PATH 10 | ${CMAKE_MODULE_PATH} 11 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake" 12 | ) 13 | 14 | IF (NOT CMAKE_BUILD_TYPE) 15 | SET(CMAKE_BUILD_TYPE MinSizeRel) 16 | ENDIF() 17 | 18 | INCLUDE(platform) 19 | INCLUDE(target) 20 | 21 | ADD_SUBDIRECTORY(src) 22 | 23 | MESSAGE(STATUS "BUILD TYPE: ${CMAKE_BUILD_TYPE}") 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ***bmod* has been merged with *[Dispar](https://github.com/netromdk/dispar)* where development continues.** 2 | 3 | bmod 4 | ==== 5 | 6 | bmod makes binary modifications and patches. 7 | -------------------------------------------------------------------------------- /cmake/compilation.cmake: -------------------------------------------------------------------------------- 1 | SET(CMAKE_CXX_FLAGS "-Wall -std=c++11") 2 | SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") 3 | SET(CMAKE_CXX_FLAGS_MINSIZEREL "-O3 -DNDEBUG") 4 | SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") 5 | SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") 6 | 7 | # Some release optimization flags for GCC/Clang. 8 | IF (NOT WIN32) 9 | # Clang/GCC 10 | SET(REL_OPTS "-pipe -fvisibility=hidden -fvisibility-inlines-hidden -ffast-math -funroll-loops") 11 | 12 | # GCC only 13 | IF ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") 14 | SET(REL_OPTS "${REL_OPTS} -fno-implement-inlines") 15 | ENDIF() 16 | 17 | SET(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} ${REL_OPTS}") 18 | SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${REL_OPTS}") 19 | SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${REL_OPTS}") 20 | ENDIF() 21 | 22 | IF ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") 23 | EXECUTE_PROCESS( 24 | COMMAND 25 | ${CMAKE_CXX_COMPILER} -dumpversion 26 | OUTPUT_VARIABLE GCC_VERSION 27 | ) 28 | IF (NOT (GCC_VERSION VERSION_GREATER 4.8 OR GCC_VERSION VERSION_EQUAL 4.8)) 29 | MESSAGE(FATAL_ERROR "Requires GCC >= 4.8.") 30 | ENDIF() 31 | ELSEIF ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") 32 | EXECUTE_PROCESS( 33 | COMMAND 34 | ${CMAKE_CXX_COMPILER} -dumpversion 35 | OUTPUT_VARIABLE CLANG_VERSION 36 | ) 37 | IF (NOT (CLANG_VERSION VERSION_GREATER 3.3 OR GCC_VERSION VERSION_EQUAL 3.3)) 38 | MESSAGE(FATAL_ERROR "Requires Clang >= 3.3.") 39 | ELSE() 40 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 41 | ENDIF() 42 | ELSEIF (MSVC AND (MSVC10 OR MSVC11 OR MSVC12)) 43 | # C++11 support is implicitly enabled. 44 | ELSE() 45 | MESSAGE(FATAL_ERROR "Your compiler does not support C++11 - aborting!") 46 | ENDIF() -------------------------------------------------------------------------------- /cmake/deps.cmake: -------------------------------------------------------------------------------- 1 | # Qt 5 2 | IF (NOT WIN32) 3 | # Added some paths to help find the modules on some systems. 4 | SET(CMAKE_PREFIX_PATH 5 | ${CMAKE_PREFIX_PATH} 6 | "/usr/local/opt" 7 | "/usr/local/opt/qt5" 8 | "/usr/local/Qt-5.3.0" 9 | "/usr/local/Qt-5.2.1" 10 | ) 11 | ENDIF() 12 | 13 | SET(CMAKE_AUTOMOC ON) # Moc when necessary. 14 | 15 | # As moc files are generated in the binary dir, tell CMake to always 16 | # look for includes there: 17 | SET(CMAKE_INCLUDE_CURRENT_DIR ON) 18 | 19 | FIND_PACKAGE(Qt5Core REQUIRED) 20 | FIND_PACKAGE(Qt5Gui REQUIRED) 21 | FIND_PACKAGE(Qt5Widgets REQUIRED) 22 | -------------------------------------------------------------------------------- /cmake/mac.cmake: -------------------------------------------------------------------------------- 1 | SET(SDK_MIN "10.8") 2 | SET(SDK "10.8") 3 | SET(DEV_SDK "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX${SDK}.sdk") 4 | 5 | IF (NOT EXISTS "${DEV_SDK}" AND NOT IS_DIRECTORY "${DEV_SDK}") 6 | MESSAGE("Could not find Mac OSX SDK at: ${DEV_SDK}") 7 | MESSAGE("Aborting!") 8 | RETURN() 9 | ENDIF() 10 | 11 | ADD_DEFINITIONS( 12 | -DMAC 13 | -DGCC_VISIBILITY 14 | -mmacosx-version-min=${SDK_MIN} 15 | ) 16 | 17 | SET(CMAKE_OSX_SYSROOT ${DEV_SDK}) 18 | -------------------------------------------------------------------------------- /cmake/platform.cmake: -------------------------------------------------------------------------------- 1 | INCLUDE(compilation) 2 | INCLUDE(deps) 3 | 4 | IF (APPLE) 5 | INCLUDE(mac) 6 | ENDIF() 7 | -------------------------------------------------------------------------------- /cmake/target.cmake: -------------------------------------------------------------------------------- 1 | SET(BIN_DIR "${CMAKE_CURRENT_BINARY_DIR}/bin") 2 | SET(LIB_DIR "${CMAKE_CURRENT_BINARY_DIR}/lib") 3 | 4 | FILE( 5 | MAKE_DIRECTORY 6 | ${BIN_DIR} 7 | ${LIB_DIR} 8 | ) 9 | 10 | SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BIN_DIR}) 11 | SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CMAKE_BUILD_TYPE} ${BIN_DIR}) 12 | SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${BIN_DIR}) 13 | SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${BIN_DIR}) 14 | 15 | SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${LIB_DIR}) 16 | SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${CMAKE_BUILD_TYPE} ${LIB_DIR}) 17 | SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${LIB_DIR}) 18 | SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${LIB_DIR}) 19 | 20 | SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${LIB_DIR}) 21 | SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CMAKE_BUILD_TYPE} ${LIB_DIR}) 22 | SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${LIB_DIR}) 23 | SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${LIB_DIR}) 24 | -------------------------------------------------------------------------------- /src/BinaryObject.cpp: -------------------------------------------------------------------------------- 1 | #include "BinaryObject.h" 2 | 3 | BinaryObject::BinaryObject(CpuType cpuType, CpuType cpuSubType, 4 | bool littleEndian, int systemBits, FileType fileType) 5 | : cpuType{cpuType}, cpuSubType{cpuSubType}, littleEndian{littleEndian}, 6 | systemBits{systemBits}, fileType{fileType} 7 | { 8 | if (cpuType == CpuType::X86_64) { 9 | this->systemBits = 64; 10 | } 11 | } 12 | 13 | QList BinaryObject::getSectionsByType(SectionType type) const { 14 | QList res; 15 | foreach (auto sec, sections) { 16 | if (sec->getType() == type) { 17 | res << sec; 18 | } 19 | } 20 | return res; 21 | } 22 | 23 | SectionPtr BinaryObject::getSection(SectionType type) const { 24 | foreach (auto sec, sections) { 25 | if (sec->getType() == type) { 26 | return sec; 27 | } 28 | } 29 | return nullptr; 30 | } 31 | -------------------------------------------------------------------------------- /src/BinaryObject.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_BINARY_OBJECT_H 2 | #define BMOD_BINARY_OBJECT_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "Section.h" 9 | #include "CpuType.h" 10 | #include "FileType.h" 11 | #include "SymbolTable.h" 12 | 13 | class BinaryObject; 14 | typedef std::shared_ptr BinaryObjectPtr; 15 | 16 | class BinaryObject { 17 | public: 18 | BinaryObject(CpuType cpuType = CpuType::X86, CpuType cpuSubType = CpuType::I386, 19 | bool littleEndian = true, int systemBits = 32, 20 | FileType fileType = FileType::Execute); 21 | 22 | CpuType getCpuType() const { return cpuType; } 23 | void setCpuType(CpuType type) { cpuType = type; } 24 | 25 | CpuType getCpuSubType() const { return cpuSubType; } 26 | void setCpuSubType(CpuType type) { cpuSubType = type; } 27 | 28 | bool isLittleEndian() const { return littleEndian; } 29 | void setLittleEndian(bool little) { littleEndian = little; } 30 | 31 | int getSystemBits() const { return systemBits; } 32 | void setSystemBits(int bits) { systemBits = bits; } 33 | 34 | FileType getFileType() const { return fileType; } 35 | void setFileType(FileType type) { fileType = type; } 36 | 37 | QList getSections() const { return sections; } 38 | QList getSectionsByType(SectionType type) const; 39 | SectionPtr getSection(SectionType type) const; 40 | void addSection(SectionPtr ptr) { sections << ptr; } 41 | 42 | void setSymbolTable(const SymbolTable &tbl) { symTable = tbl; } 43 | const SymbolTable &getSymbolTable() const { return symTable; } 44 | 45 | void setDynSymbolTable(const SymbolTable &tbl) { dynsymTable = tbl; } 46 | const SymbolTable &getDynSymbolTable() const { return dynsymTable; } 47 | 48 | private: 49 | CpuType cpuType, cpuSubType; 50 | bool littleEndian; 51 | int systemBits; 52 | FileType fileType; 53 | QList sections; 54 | SymbolTable symTable, dynsymTable; 55 | }; 56 | 57 | #endif // BMOD_BINARY_OBJECT_H 58 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | SET(NAME bmod) 2 | 3 | ADD_EXECUTABLE( 4 | ${NAME} 5 | 6 | main.cpp 7 | 8 | Util.h 9 | Util.cpp 10 | 11 | Config.h 12 | Config.cpp 13 | 14 | Reader.h 15 | Reader.cpp 16 | 17 | Section.h 18 | Section.cpp 19 | 20 | BinaryObject.h 21 | BinaryObject.cpp 22 | SymbolTable.h 23 | SymbolTable.cpp 24 | 25 | widgets/MainWindow.h 26 | widgets/MainWindow.cpp 27 | widgets/TreeWidget.h 28 | widgets/TreeWidget.cpp 29 | widgets/LineEdit.h 30 | widgets/LineEdit.cpp 31 | widgets/BinaryWidget.h 32 | widgets/BinaryWidget.cpp 33 | widgets/MachineCodeWidget.h 34 | widgets/MachineCodeWidget.cpp 35 | widgets/ConversionHelper.h 36 | widgets/ConversionHelper.cpp 37 | widgets/DisassemblerDialog.h 38 | widgets/DisassemblerDialog.cpp 39 | widgets/PreferencesDialog.h 40 | widgets/PreferencesDialog.cpp 41 | 42 | panes/Pane.h 43 | panes/ArchPane.h 44 | panes/ArchPane.cpp 45 | panes/ProgramPane.h 46 | panes/ProgramPane.cpp 47 | panes/DisassemblyPane.h 48 | panes/DisassemblyPane.cpp 49 | panes/StringsPane.h 50 | panes/StringsPane.cpp 51 | panes/SymbolsPane.h 52 | panes/SymbolsPane.cpp 53 | panes/GenericPane.h 54 | panes/GenericPane.cpp 55 | 56 | formats/Format.h 57 | formats/Format.cpp 58 | formats/MachO.h 59 | formats/MachO.cpp 60 | 61 | asm/Asm.h 62 | asm/AsmX86.h 63 | asm/AsmX86.cpp 64 | asm/Disassembler.h 65 | asm/Disassembler.cpp 66 | ) 67 | 68 | QT5_USE_MODULES(${NAME} Core Gui Widgets) 69 | -------------------------------------------------------------------------------- /src/Config.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "Config.h" 5 | 6 | Config::Config() 7 | : settings(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/bmod.ini", 8 | QSettings::IniFormat) 9 | { 10 | qDebug() << "Using config:" << qPrintable(settings.fileName()); 11 | load(); 12 | } 13 | 14 | Config::~Config() { 15 | save(); 16 | } 17 | 18 | void Config::load() { 19 | settings.beginReadArray("General"); 20 | confirmCommit = settings.value("confirmCommit", true).toBool(); 21 | confirmQuit = settings.value("confirmQuit", true).toBool(); 22 | settings.endArray(); 23 | 24 | settings.beginReadArray("Backups"); 25 | backupEnabled = settings.value("backupEnabled", true).toBool(); 26 | backupAsk = settings.value("backupAsk", true).toBool(); 27 | backupAmount = settings.value("backupAmount", 5).toInt(); 28 | if (backupAmount < 0) backupAmount = 0; 29 | settings.endArray(); 30 | } 31 | 32 | void Config::save() { 33 | settings.clear(); 34 | 35 | settings.beginGroup("General"); 36 | settings.setValue("confirmCommit", confirmCommit); 37 | settings.setValue("confirmQuit", confirmQuit); 38 | settings.endGroup(); 39 | 40 | settings.beginGroup("Backups"); 41 | settings.setValue("backupEnabled", backupEnabled); 42 | settings.setValue("backupAsk", backupAsk); 43 | settings.setValue("backupAmount", backupAmount); 44 | settings.endGroup(); 45 | 46 | settings.sync(); 47 | } 48 | -------------------------------------------------------------------------------- /src/Config.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_CONFIG_H 2 | #define BMOD_CONFIG_H 3 | 4 | #include 5 | 6 | class Config { 7 | public: 8 | Config(); 9 | ~Config(); 10 | 11 | void load(); 12 | void save(); 13 | 14 | bool getConfirmCommit() const { return confirmCommit; } 15 | void setConfirmCommit(bool confirm) { confirmCommit = confirm; } 16 | 17 | bool getConfirmQuit() const { return confirmQuit; } 18 | void setConfirmQuit(bool confirm) { confirmQuit = confirm; } 19 | 20 | bool getBackupEnabled() const { return backupEnabled; } 21 | void setBackupEnabled(bool enabled) { backupEnabled = enabled; } 22 | 23 | bool getBackupAsk() const { return backupAsk; } 24 | void setBackupAsk(bool ask) { this->backupAsk = ask; } 25 | 26 | int getBackupAmount() const { return backupAmount; } 27 | void setBackupAmount(int amount) { backupAmount = amount; } 28 | 29 | private: 30 | QSettings settings; 31 | 32 | // General 33 | bool confirmCommit, confirmQuit; 34 | 35 | // Backup 36 | bool backupEnabled, backupAsk; 37 | int backupAmount; 38 | }; 39 | 40 | #endif // BMOD_CONFIG_H 41 | -------------------------------------------------------------------------------- /src/CpuType.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_CPU_TYPE_H 2 | #define BMOD_CPU_TYPE_H 3 | 4 | enum class CpuType : int { 5 | X86, // Same as i386 6 | X86_64, 7 | HPPA, 8 | ARM, 9 | SPARC, 10 | I860, 11 | PowerPC, 12 | PowerPC_64, 13 | 14 | // Sub types 15 | I386, 16 | I486, 17 | I486_SX, 18 | Pentium, 19 | PentiumPro, 20 | PentiumII_M3, 21 | PentiumII_M5, 22 | Celeron, 23 | CeleronMobile, 24 | Pentium_3, 25 | Pentium_3_M, 26 | Pentium_3_Xeon, 27 | Pentium_M, 28 | Pentium_4, 29 | Pentium_4_M, 30 | Itanium, 31 | Itanium_2, 32 | Xeon, 33 | Xeon_MP 34 | }; 35 | 36 | #endif // BMOD_CPU_TYPE_H 37 | -------------------------------------------------------------------------------- /src/FileType.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_FILE_TYPE_H 2 | #define BMOD_FILE_TYPE_H 3 | 4 | enum class FileType : int { 5 | Object, // Intermediate object file (.o). 6 | Execute, // Executable file. 7 | Core, // Crash report. 8 | Preload, // Special-purpose program, like programs burned into ROM chips. 9 | Dylib, // Dynamic shared library (.dylib). 10 | Dylinker, // Dynamic linker shared library. 11 | Bundle // Bundle/plugin (.bundle). 12 | }; 13 | 14 | #endif // BMOD_FILE_TYPE_H 15 | -------------------------------------------------------------------------------- /src/Reader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "Reader.h" 5 | 6 | Reader::Reader(QIODevice &dev, bool littleEndian) 7 | : dev{dev}, littleEndian{littleEndian} 8 | { } 9 | 10 | quint16 Reader::getUInt16(bool *ok) { 11 | return getUInt(ok); 12 | } 13 | 14 | quint32 Reader::getUInt32(bool *ok) { 15 | return getUInt(ok); 16 | } 17 | 18 | quint64 Reader::getUInt64(bool *ok) { 19 | return getUInt(ok); 20 | } 21 | 22 | char Reader::getChar(bool *ok) { 23 | char c{0}; 24 | bool res = dev.getChar(&c); 25 | if (ok) *ok = res; 26 | return c; 27 | } 28 | 29 | unsigned char Reader::getUChar(bool *ok) { 30 | return (unsigned char) getChar(ok); 31 | } 32 | 33 | char Reader::peekChar(bool *ok) { 34 | char c{0}; 35 | qint64 num = dev.peek(&c, 1); 36 | if (ok) *ok = (num == 1); 37 | return c; 38 | } 39 | 40 | unsigned char Reader::peekUChar(bool *ok) { 41 | return (unsigned char) peekChar(ok); 42 | } 43 | 44 | QByteArray Reader::read(qint64 max) { 45 | return dev.read(max); 46 | } 47 | 48 | qint64 Reader::pos() const { 49 | return dev.pos(); 50 | } 51 | 52 | bool Reader::seek(qint64 pos) { 53 | return dev.seek(pos); 54 | } 55 | 56 | bool Reader::atEnd() const { 57 | return dev.atEnd(); 58 | } 59 | 60 | bool Reader::peekList(std::initializer_list list) { 61 | if (list.size() == 0) { 62 | return false; 63 | } 64 | const QByteArray parr = dev.peek(list.size()); 65 | if (parr.size() != list.size()) { 66 | return false; 67 | } 68 | int i{0}; 69 | for (auto it = list.begin(); it != list.end(); it++, i++) { 70 | if (*it != (unsigned char) parr[i]) { 71 | return false; 72 | } 73 | } 74 | return true; 75 | } 76 | 77 | template 78 | T Reader::getUInt(bool *ok) { 79 | constexpr int num = sizeof(T); 80 | QByteArray buf = dev.read(num); 81 | if (buf.size() < num) { 82 | if (ok) *ok = false; 83 | return 0; 84 | } 85 | T res{0}; 86 | for (int i = 0; i < num; i++) { 87 | int j = i; 88 | if (!littleEndian) { 89 | j = num - (i + 1); 90 | } 91 | res += ((T) (unsigned char) buf[i]) << j * 8; 92 | } 93 | if (ok) *ok = true; 94 | return res; 95 | } 96 | -------------------------------------------------------------------------------- /src/Reader.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_READER_H 2 | #define BMOD_READER_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | class QIODevice; 9 | 10 | class Reader; 11 | typedef std::unique_ptr ReaderPtr; 12 | 13 | class Reader { 14 | public: 15 | Reader(QIODevice &dev, bool littleEndian = true); 16 | 17 | bool isLittleEndian() const { return littleEndian; } 18 | void setLittleEndian(bool little) { littleEndian = little; } 19 | 20 | quint16 getUInt16(bool *ok = nullptr); 21 | quint32 getUInt32(bool *ok = nullptr); 22 | quint64 getUInt64(bool *ok = nullptr); 23 | 24 | char getChar(bool *ok = nullptr); 25 | unsigned char getUChar(bool *ok = nullptr); 26 | char peekChar(bool *ok = nullptr); 27 | unsigned char peekUChar(bool *ok = nullptr); 28 | 29 | QByteArray read(qint64 max); 30 | 31 | qint64 pos() const; 32 | bool seek(qint64 pos); 33 | bool atEnd() const; 34 | 35 | bool peekList(std::initializer_list list); 36 | 37 | private: 38 | template 39 | T getUInt(bool *ok = nullptr); 40 | 41 | QIODevice &dev; 42 | bool littleEndian; 43 | }; 44 | 45 | #endif // BMOD_READER_H 46 | -------------------------------------------------------------------------------- /src/Section.cpp: -------------------------------------------------------------------------------- 1 | #include "Section.h" 2 | 3 | Section::Section(SectionType type, const QString &name, quint64 addr, 4 | quint64 size, quint32 offset) 5 | : type{type}, name{name}, addr{addr}, size{size}, offset{offset} 6 | { } 7 | 8 | void Section::setSubData(const QByteArray &subData, int pos) { 9 | if (pos < 0 || pos > data.size() - 1) { 10 | return; 11 | } 12 | data.replace(pos, subData.size(), subData); 13 | modified = QDateTime::currentDateTime(); 14 | 15 | QPair region(pos, subData.size()); 16 | if (!modifiedRegions.contains(region)) { 17 | modifiedRegions << region; 18 | } 19 | } 20 | 21 | const QList> &Section::getModifiedRegions() const { 22 | return modifiedRegions; 23 | } 24 | -------------------------------------------------------------------------------- /src/Section.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_SECTION_H 2 | #define BMOD_SECTION_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "SectionType.h" 13 | 14 | class Section; 15 | typedef std::shared_ptr
SectionPtr; 16 | 17 | class Section { 18 | public: 19 | Section(SectionType type, const QString &name, quint64 addr, quint64 size, 20 | quint32 offset = 0); 21 | 22 | SectionType getType() const { return type; } 23 | QString getName() const { return name; } 24 | quint64 getAddress() const { return addr; } 25 | quint64 getSize() const { return size; } 26 | quint32 getOffset() const { return offset; } 27 | 28 | const QByteArray &getData() const { return data; } 29 | void setData(const QByteArray &data) { this->data = data; } 30 | 31 | void setSubData(const QByteArray &subData, int pos); 32 | bool isModified() const { return !modifiedRegions.isEmpty(); } 33 | QDateTime modifiedWhen() const { return modified; } 34 | const QList> &getModifiedRegions() const; 35 | 36 | private: 37 | SectionType type; 38 | QString name; 39 | quint64 addr, size; 40 | quint32 offset; 41 | QByteArray data; 42 | QList> modifiedRegions; 43 | QDateTime modified; 44 | }; 45 | 46 | #endif // BMOD_SECTION_H 47 | -------------------------------------------------------------------------------- /src/SectionType.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_SECTION_TYPE_H 2 | #define BMOD_SECTION_TYPE_H 3 | 4 | enum class SectionType : int { 5 | Text, // Executable code (__text, .text). 6 | SymbolStubs, // Indirect symbol stubs. 7 | Symbols, // Symbol table. 8 | DynSymbols, // Dynamic symbol table. 9 | CString, // Constant C strings (__cstring). 10 | String, // String table constants. 11 | FuncStarts, // Function starts. 12 | CodeSig, // Code signature. 13 | }; 14 | 15 | #endif // BMOD_SECTION_TYPE_H 16 | -------------------------------------------------------------------------------- /src/SymbolTable.cpp: -------------------------------------------------------------------------------- 1 | #include "SymbolTable.h" 2 | 3 | void SymbolTable::addSymbol(const SymbolEntry &entry) { 4 | entries << entry; 5 | } 6 | 7 | bool SymbolTable::getString(quint64 value, QString &str) const { 8 | foreach (const auto &entry, entries) { 9 | if (entry.getValue() == value) { 10 | const auto &s = entry.getString(); 11 | if (s.isEmpty()) continue; 12 | str = s; 13 | return true; 14 | } 15 | } 16 | return false; 17 | } 18 | -------------------------------------------------------------------------------- /src/SymbolTable.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_SYMBOL_TABLE_H 2 | #define BMOD_SYMBOL_TABLE_H 3 | 4 | #include 5 | #include 6 | 7 | class SymbolEntry { 8 | public: 9 | SymbolEntry(quint32 index, quint64 value, 10 | const QString &strValue = QString()) 11 | : index{index}, value{value}, strValue{strValue} 12 | { } 13 | 14 | quint32 getIndex() const { return index; } 15 | 16 | void setValue(quint64 value) { this->value = value; } 17 | quint64 getValue() const { return value; } 18 | 19 | void setString(const QString &str) { strValue = str; } 20 | const QString &getString() const { return strValue; } 21 | 22 | private: 23 | quint32 index; // of string table 24 | quint64 value; // of symbol 25 | QString strValue; // String table value 26 | }; 27 | 28 | class SymbolTable { 29 | public: 30 | void addSymbol(const SymbolEntry &entry); 31 | QList &getSymbols() { return entries; } 32 | const QList &getSymbols() const { return entries; } 33 | 34 | bool getString(quint64 value, QString &str) const; 35 | 36 | private: 37 | QList entries; 38 | }; 39 | 40 | #endif // BMOD_SYMBOL_TABLE_H 41 | -------------------------------------------------------------------------------- /src/Util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Util.h" 9 | 10 | QString Util::formatTypeString(FormatType type) { 11 | switch (type) { 12 | default: 13 | case FormatType::MachO: 14 | return "Mach-O"; 15 | } 16 | } 17 | 18 | QString Util::cpuTypeString(CpuType type) { 19 | switch (type) { 20 | default: 21 | case CpuType::X86: 22 | return "x86"; 23 | 24 | case CpuType::X86_64: 25 | return "x86 64"; 26 | 27 | case CpuType::HPPA: 28 | return "HPPA"; 29 | 30 | case CpuType::ARM: 31 | return "ARM"; 32 | 33 | case CpuType::SPARC: 34 | return "SPARC"; 35 | 36 | case CpuType::I860: 37 | return "i860"; 38 | 39 | case CpuType::PowerPC: 40 | return "PowerPC"; 41 | 42 | case CpuType::PowerPC_64: 43 | return "PowerPC 64"; 44 | 45 | case CpuType::I386: 46 | return "i386"; 47 | 48 | case CpuType::I486: 49 | return "i486"; 50 | 51 | case CpuType::I486_SX: 52 | return "i486 SX"; 53 | 54 | case CpuType::Pentium: 55 | return "Pentium"; 56 | 57 | case CpuType::PentiumPro: 58 | return "Pentium Pro"; 59 | 60 | case CpuType::PentiumII_M3: 61 | return "Pentium II M3"; 62 | 63 | case CpuType::PentiumII_M5: 64 | return "Pentium II M5"; 65 | 66 | case CpuType::Celeron: 67 | return "Celeron"; 68 | 69 | case CpuType::CeleronMobile: 70 | return "Celeron Mobile"; 71 | 72 | case CpuType::Pentium_3: 73 | return "Pentium 3"; 74 | 75 | case CpuType::Pentium_3_M: 76 | return "Pentium 3 M"; 77 | 78 | case CpuType::Pentium_3_Xeon: 79 | return "Pentium 3 Xeon"; 80 | 81 | case CpuType::Pentium_M: 82 | return "Pentium M"; 83 | 84 | case CpuType::Pentium_4: 85 | return "Pentium 4"; 86 | 87 | case CpuType::Pentium_4_M: 88 | return "Pentium 4 M"; 89 | 90 | case CpuType::Itanium: 91 | return "Itanium"; 92 | 93 | case CpuType::Itanium_2: 94 | return "Itanium 2"; 95 | 96 | case CpuType::Xeon: 97 | return "Xeon"; 98 | 99 | case CpuType::Xeon_MP: 100 | return "Xeon MP"; 101 | } 102 | } 103 | 104 | QString Util::fileTypeString(FileType type) { 105 | switch (type) { 106 | case FileType::Object: 107 | return "Object"; 108 | 109 | default: 110 | case FileType::Execute: 111 | return "Executable"; 112 | 113 | case FileType::Core: 114 | return "Core"; 115 | 116 | case FileType::Preload: 117 | return "Preloaded Program"; 118 | 119 | case FileType::Dylib: 120 | return "Dylib"; 121 | 122 | case FileType::Dylinker: 123 | return "Dylinker"; 124 | 125 | case FileType::Bundle: 126 | return "Bundle"; 127 | } 128 | } 129 | 130 | QString Util::sectionTypeString(SectionType type) { 131 | switch (type) { 132 | default: 133 | case SectionType::Text: 134 | return "Text"; 135 | 136 | case SectionType::CString: 137 | return "CString"; 138 | } 139 | } 140 | 141 | void Util::centerWidget(QWidget *widget) { 142 | widget->move(QApplication::desktop()->screen()->rect().center() 143 | - widget->rect().center()); 144 | } 145 | 146 | QString Util::formatSize(qint64 bytes, int digits) { 147 | constexpr double KB = 1024, MB = 1024 * KB, GB = 1024 * MB, TB = 1024 * GB; 148 | QString unit{"B"}; 149 | double size = bytes; 150 | if (size >= TB) { 151 | size /= TB; 152 | unit = "TB"; 153 | } 154 | else if (size >= GB) { 155 | size /= GB; 156 | unit = "GB"; 157 | } 158 | else if (size >= MB) { 159 | size /= MB; 160 | unit = "MB"; 161 | } 162 | else if (size >= KB) { 163 | size /= KB; 164 | unit = "KB"; 165 | } 166 | return QString("%1 %2").arg(QString::number(size, 'f', digits)).arg(unit); 167 | } 168 | 169 | QString Util::padString(const QString &str, int size, bool before, char pad) { 170 | int addrLen = str.size(); 171 | if (addrLen < size) { 172 | QString res{str}, padStr{QString(size - addrLen, pad)}; 173 | return (before ? padStr + res : res + padStr); 174 | } 175 | return str; 176 | } 177 | 178 | QString Util::dataToAscii(const QByteArray &data, int offset, int size) { 179 | QString res; 180 | int len = data.size(); 181 | for (int i = offset; i - offset < size && i < len; i++) { 182 | int ic = data[i]; 183 | res += (ic >= 32 && ic <= 126 ? (char) ic : '.'); 184 | } 185 | return res; 186 | } 187 | 188 | QString Util::hexToAscii(const QString &str, int offset, int blocks, 189 | bool unicode) { 190 | QString res; 191 | int len = str.size(); 192 | int size = blocks * 2; 193 | bool ok; 194 | for (int i = offset; i - offset <= size && i < len; i += 2) { 195 | if (str[i] == ' ') { 196 | size++; 197 | i--; 198 | continue; 199 | } 200 | int ic = str.mid(i, 2).toInt(&ok, 16); 201 | if (!ok) return QString(); 202 | if (!unicode) { 203 | res += (ic >= 32 && ic <= 126 ? (char) ic : '.'); 204 | } 205 | else { 206 | res += QString::fromUtf16((ushort*) &ic, 1); 207 | } 208 | } 209 | return res; 210 | } 211 | 212 | QString Util::hexToString(const QString &str) { 213 | return QString::fromUtf8(hexToData(str)); 214 | } 215 | 216 | QByteArray Util::hexToData(const QString &str) { 217 | QByteArray data; 218 | int size = str.size(); 219 | bool ok; 220 | for (int i = 0; i < size; i += 2) { 221 | data += str.mid(i, 2).toInt(&ok, 16); 222 | if (!ok) return QByteArray(); 223 | } 224 | return data; 225 | } 226 | 227 | QString Util::resolveAppBinary(const QString &path) { 228 | if (!path.toLower().endsWith(".app")) { 229 | return QString(); 230 | } 231 | QDir dir(path); 232 | if (dir.exists() && dir.cd("Contents") && dir.cd("MacOS")) { 233 | QFileInfo fi(path); 234 | if (dir.exists(fi.baseName())) { 235 | return dir.absoluteFilePath(fi.baseName()); 236 | } 237 | } 238 | return QString(); 239 | } 240 | 241 | void Util::setTreeItemMarked(QTreeWidgetItem *item, int column) { 242 | auto font = item->font(column); 243 | font.setBold(true); 244 | item->setFont(column, font); 245 | item->setForeground(column, Qt::red); 246 | } 247 | 248 | QString Util::addrDataString(quint64 addr, QByteArray data) { 249 | // Pad data to a multiple of 16. 250 | quint64 rest = data.size() % 16; 251 | if (rest != 0) { 252 | int amount = 16 - rest; 253 | for (int i = 0; i < amount; i++) { 254 | data += (char) 0; 255 | } 256 | } 257 | 258 | QString output = QString::number(addr, 16).toUpper() + ": "; 259 | QString ascii; 260 | for (int i = 0; i < data.size(); i++) { 261 | char c = data[i]; 262 | int ic = c; 263 | unsigned char uc = c; 264 | QString hc = QString::number(uc, 16).toUpper(); 265 | if (hc.size() == 1) { 266 | hc = "0" + hc; 267 | } 268 | output += hc + " "; 269 | ascii += (ic >= 33 && ic <= 126 ? c : '.'); 270 | if ((i + 1) % 16 == 0 || i == data.size() - 1) { 271 | output += " " + ascii; 272 | ascii.clear(); 273 | if (i != data.size() - 1) { 274 | addr += 16; 275 | output += "\n" + QString::number(addr, 16).toUpper() + ": "; 276 | } 277 | } 278 | } 279 | return output; 280 | } 281 | -------------------------------------------------------------------------------- /src/Util.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_UTIL_H 2 | #define BMOD_UTIL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "Section.h" 8 | #include "CpuType.h" 9 | #include "FileType.h" 10 | #include "formats/FormatType.h" 11 | 12 | class QWidget; 13 | class QTreeWidgetItem; 14 | 15 | class Util { 16 | public: 17 | static QString formatTypeString(FormatType type); 18 | static QString cpuTypeString(CpuType type); 19 | static QString fileTypeString(FileType type); 20 | static QString sectionTypeString(SectionType type); 21 | 22 | static void centerWidget(QWidget *widget); 23 | static QString formatSize(qint64 bytes, int digits = 1); 24 | 25 | // char(48) = '0' 26 | static QString padString(const QString &str, int size, bool before = true, 27 | char pad = 48); 28 | 29 | static QString dataToAscii(const QByteArray &data, int offset, int size); 30 | static QString hexToAscii(const QString &data, int offset, int blocks, 31 | bool unicode = false); 32 | static QString hexToString(const QString &str); 33 | static QByteArray hexToData(const QString &str); 34 | 35 | static QString resolveAppBinary(const QString &path); 36 | 37 | static void setTreeItemMarked(QTreeWidgetItem *item, int column); 38 | 39 | /** 40 | * Generate string of format: 41 | * 42 | * ADDR: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F . . . . . . . . . . . . . . . . 43 | * ADDR: 02 00 01 04 03 06 07 05 08 09 0A 0D 0B 0C 0F 0E . . . . . . . . . . . . . . . . 44 | * 45 | * And so on, where ADDR is the address followed by data in hex and 46 | * ASCII representation of meaningful values. 47 | */ 48 | static QString addrDataString(quint64 addr, QByteArray data); 49 | }; 50 | 51 | #endif // BMOD_UTIL_H 52 | -------------------------------------------------------------------------------- /src/Version.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_VERSION_H 2 | #define BMOD_VERSION_H 3 | 4 | #include 5 | #include 6 | 7 | #define MAJOR_VERSION 0 8 | #define MINOR_VERSION 1 9 | #define BUILD_VERSION 0 10 | 11 | #define BUILD_DATE "June 4, 2014" 12 | 13 | #define MAJOR_FACTOR 1000000 14 | #define MINOR_FACTOR 1000 15 | 16 | static inline QString versionString(int major, int minor, int build, 17 | bool showDate = false) { 18 | return QString::number(major) + "." + QString::number(minor) + "." + 19 | QString::number(build) + (showDate ? " [" + QString(BUILD_DATE) + "]" : ""); 20 | } 21 | 22 | static inline QString versionString(bool showDate = false) { 23 | return versionString(MAJOR_VERSION, MINOR_VERSION, BUILD_VERSION, showDate); 24 | } 25 | 26 | #endif // BMOD_VERSION_H 27 | -------------------------------------------------------------------------------- /src/asm/Asm.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_ASM_H 2 | #define BMOD_ASM_H 3 | 4 | #include "../Section.h" 5 | #include "Disassembler.h" 6 | 7 | class Asm { 8 | public: 9 | virtual ~Asm() { } 10 | virtual bool disassemble(SectionPtr sec, Disassembly &result) =0; 11 | }; 12 | 13 | #endif // BMOD_ASM_H 14 | -------------------------------------------------------------------------------- /src/asm/AsmX86.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * X86 and X86_64 disassembling. 3 | * 4 | * Uses the AT&T/GAS assembly syntax: Mnemonic Src Dst 5 | * 6 | * References: 7 | * http://www.read.seas.harvard.edu/~kohler/class/04f-aos/ref/i386.pdf 8 | * http://ref.x86asm.net 9 | */ 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include "AsmX86.h" 17 | #include "../Util.h" 18 | #include "../Reader.h" 19 | #include "../Section.h" 20 | 21 | namespace { 22 | QString Instruction::toString(BinaryObjectPtr obj) const { 23 | QString str(mnemonic); 24 | switch (dataType) { 25 | case DataType::None: 26 | // Nothing. 27 | break; 28 | 29 | case DataType::Byte: 30 | str += "b"; 31 | break; 32 | 33 | case DataType::Word: 34 | str += "w"; 35 | break; 36 | 37 | case DataType::Doubleword: 38 | str += "l"; 39 | break; 40 | 41 | case DataType::Quadword: 42 | str += "q"; 43 | break; 44 | } 45 | 46 | // Annotate calls with symbols if present. 47 | if (call && dispDst) { 48 | str += " " + getDispString(); 49 | quint64 addr = disp + offset; 50 | const auto &symTable = obj->getSymbolTable(); 51 | const auto &dynsymTable = obj->getDynSymbolTable(); 52 | QString name; 53 | if (symTable.getString(addr, name) || dynsymTable.getString(addr, name)) { 54 | str += " (" + name + ")"; 55 | } 56 | return str; 57 | } 58 | 59 | bool comma{true}; 60 | if (srcRegSet) { 61 | if (!str.endsWith(" ")) str += " "; 62 | if (immSrc) { 63 | str += getImmString() + ","; 64 | } 65 | if (dispSrc) { 66 | str += getDispString() + "("; 67 | } 68 | str += getRegString(srcReg, srcRegType, 69 | // Using SREG because it's the greatest value. 70 | dispSrc ? RegType::SREG : srcRegType); 71 | if (dispSrc) { 72 | str += ")"; 73 | } 74 | } 75 | else if (sipSrc) { 76 | if (!str.endsWith(" ")) str += " "; 77 | if (dispSrc) { 78 | str += getDispString(); 79 | } 80 | str += getSipString(srcRegType); 81 | } 82 | else if (dispSrc) { 83 | if (!str.endsWith(" ")) str += " "; 84 | str += getDispString(); 85 | } 86 | else if (immSrc) { 87 | if (!str.endsWith(" ")) str += " "; 88 | str += getImmString(); 89 | } 90 | else { 91 | comma = false; 92 | } 93 | 94 | if (dstRegSet) { 95 | if (!comma && !str.endsWith(" ")) { 96 | str += " "; 97 | } 98 | else { 99 | str += ","; 100 | } 101 | if (immDst) { 102 | str += getImmString() + ","; 103 | } 104 | if (dispDst) { 105 | str += getDispString() + "("; 106 | } 107 | str += getRegString(dstReg, dstRegType, 108 | dispDst ? RegType::SREG : dstRegType); 109 | if (dispDst) { 110 | str += ")"; 111 | } 112 | } 113 | else if (sipDst) { 114 | if (!comma && !str.endsWith(" ")) { 115 | str += " "; 116 | } 117 | else { 118 | str += ","; 119 | } 120 | if (dispDst) { 121 | str += getDispString(); 122 | } 123 | str += getSipString(dstRegType); 124 | } 125 | else if (dispDst) { 126 | if (!str.endsWith(" ")) str += " "; 127 | str += getDispString(); 128 | } 129 | else if (immDst) { 130 | if (!str.endsWith(" ")) str += " "; 131 | str += getImmString(); 132 | } 133 | return str; 134 | } 135 | 136 | void Instruction::reverse() { 137 | qSwap(srcReg, dstReg); 138 | qSwap(srcRegSet, dstRegSet); 139 | qSwap(srcRegType, dstRegType); 140 | qSwap(sipSrc, sipDst); 141 | qSwap(dispSrc, dispDst); 142 | qSwap(immSrc, immDst); 143 | } 144 | 145 | QString Instruction::getRegString(int reg, RegType type, 146 | RegType type2) const { 147 | if (reg < 0 || reg > 17) { 148 | return QString(); 149 | } 150 | static QString regs[8][17] = 151 | { // R8 without REX prefix 152 | {"al", "cl", "dl", "bl", "ah", "ch", "dh", "bh", 153 | "inva", "inva", "inva", "inva", "inva", "inva", "inva", "inva", "inva"}, 154 | 155 | // R8R with any REX prefix 156 | {"al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil", 157 | "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b", // REX.R=1 158 | "inva"}, 159 | 160 | // R16 161 | {"ax", "cx", "dx", "bx", "sp", "bp", "si", "di", 162 | "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w", // REX.R=1 163 | "inva"}, 164 | 165 | // R32 166 | {"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", 167 | "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d", // REX.R=1 168 | "eip"}, 169 | 170 | // R64 171 | {"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", 172 | "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", // REX.R=1 173 | "rip"}, 174 | 175 | // MM 176 | {"mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7", 177 | "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7", // REX.R=1 178 | "inva"}, 179 | 180 | // XMM 181 | {"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", 182 | "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", // REX.R=1 183 | "inva"}, 184 | 185 | // SREG 186 | {"es", "cs", "ss", "ds", "fs", "gs", "resv", "resv", 187 | "es", "cs", "ss", "ds", "fs", "gs", "resv", "resv", // REX.R=1 188 | "inva"}}; 189 | QString res = "%" + regs[(int) type][reg]; 190 | 191 | // If opposite type is less than then mark as address reference. 192 | if ((int) type > (int) type2) { 193 | res = "(" + res + ")"; 194 | } 195 | return res; 196 | } 197 | 198 | QString Instruction::getSipString(RegType type) const { 199 | QString sip{"("}; 200 | sip += getRegString(base, type); 201 | if (index != 4) { 202 | sip += "," + getRegString(index, type) + 203 | "," + QString::number(pow(2, scale)); 204 | } 205 | return sip + ")"; 206 | } 207 | 208 | QString Instruction::getDispString() const { 209 | return formatHex(disp + offset, dispBytes * 2); 210 | } 211 | 212 | QString Instruction::getImmString() const { 213 | return "$" + formatHex(imm + offset, immBytes * 2); 214 | } 215 | 216 | QString Instruction::formatHex(quint64 num, int len) const { 217 | return "0x" + Util::padString(QString::number(num, 16).toUpper(), len); 218 | } 219 | } 220 | 221 | AsmX86::AsmX86(BinaryObjectPtr obj) : obj{obj}, reader{nullptr} { } 222 | 223 | bool AsmX86::disassemble(SectionPtr sec, Disassembly &result) { 224 | QBuffer buf; 225 | buf.setData(sec->getData()); 226 | buf.open(QIODevice::ReadOnly); 227 | reader.reset(new Reader(buf)); 228 | 229 | // Address of main() 230 | quint64 funcAddr = sec->getAddress(); 231 | 232 | bool ok{true}, peek{false}; 233 | unsigned char ch, nch; 234 | qint64 pos{0}; 235 | Instruction inst; 236 | const bool _64 = (obj->getSystemBits() == 64); 237 | while (!reader->atEnd()) { 238 | // Handle special NOP sequences. 239 | if (handleNops(result)) { 240 | continue; 241 | } 242 | 243 | pos = reader->pos(); 244 | ch = reader->getUChar(&ok); 245 | if (!ok) return false; 246 | 247 | nch = reader->peekUChar(&peek); 248 | 249 | // Instruction to fill. 250 | inst = Instruction(); 251 | inst.dataType = DataType::Doubleword; 252 | inst.srcRegType = inst.dstRegType = (_64 ? RegType::R64 : RegType::R32); 253 | 254 | // REX mode (64-bit ONLY!). 255 | if (_64 && ch >= 0x40 && ch <= 0x4F) { 256 | // W=1 => 64-bit 257 | // R=1 => Extension to Mod-R/M: reg 258 | // X=1 => Extension to SIB index 259 | // B=1 => Extension to Mod-R/M: R/M oR SIB base 260 | splitRex(ch, inst.rexW, inst.rexR, inst.rexX, inst.rexB); 261 | if (inst.rexW) { 262 | inst.dataType = DataType::Quadword; 263 | } 264 | 265 | // Setup for next. 266 | ch = reader->getUChar(&ok); 267 | if (!ok) return false; 268 | 269 | nch = reader->peekUChar(&peek); 270 | } 271 | 272 | // ADD (r/m16/32 r16/32) (reverse of 0x03) 273 | if (ch == 0x01 && peek) { 274 | inst.mnemonic = "add"; 275 | processModRegRM(inst); 276 | inst.reverse(); 277 | addResult(inst, pos, result); 278 | } 279 | 280 | // ADD (r16/32 r/m16/32) 281 | else if (ch == 0x03 && peek) { 282 | inst.mnemonic = "add"; 283 | processModRegRM(inst); 284 | addResult(inst, pos, result); 285 | } 286 | 287 | // ADD (eAX imm16/32) 288 | else if (ch == 0x05) { 289 | inst.mnemonic = "add"; 290 | inst.imm = reader->getUInt32(); 291 | inst.immBytes = 4; 292 | inst.immSrc = true; 293 | 294 | // Dst is always %eax. 295 | inst.dstReg = 0; 296 | inst.dstRegSet = true; 297 | 298 | addResult(inst, pos, result); 299 | } 300 | 301 | // PUSH (imm8) 302 | else if (ch == 0x6A && peek) { 303 | inst.mnemonic = "push"; 304 | if (_64) inst.dataType = DataType::Quadword; 305 | inst.immDst = true; 306 | inst.dstRegSet = false; 307 | processImm8(inst); 308 | addResult(inst, pos, result); 309 | } 310 | 311 | // OR (r/m16/32 r16/32) 312 | else if (ch == 0x09) { 313 | inst.mnemonic = "or"; 314 | processModRegRM(inst); 315 | inst.reverse(); 316 | addResult(inst, pos, result); 317 | } 318 | 319 | // OR (eAX imm16/32) 320 | else if (ch == 0x0D) { 321 | inst.mnemonic = "or"; 322 | inst.imm = reader->getUInt32(); 323 | inst.immBytes = 4; 324 | inst.immSrc = true; 325 | 326 | // Dst is always %eax. 327 | inst.dstReg = 0; 328 | inst.dstRegSet = true; 329 | 330 | addResult(inst, pos, result); 331 | } 332 | 333 | // AND (r16/32 r/m16/32) 334 | else if (ch == 0x23 && peek) { 335 | inst.mnemonic = "and"; 336 | processModRegRM(inst); 337 | addResult(inst, pos, result); 338 | } 339 | 340 | // AND (AL imm8) 341 | else if (ch == 0x24) { 342 | inst.mnemonic = "and"; 343 | inst.dataType = DataType::Byte; 344 | inst.srcRegType = inst.dstRegType = RegType::R8; 345 | inst.imm = reader->getUChar(); 346 | inst.immBytes = 1; 347 | inst.immSrc = true; 348 | 349 | // Dst is always %al. 350 | inst.dstReg = 0; 351 | inst.dstRegSet = true; 352 | 353 | addResult(inst, pos, result); 354 | } 355 | 356 | // AND (eAX imm16/32) 357 | else if (ch == 0x25) { 358 | inst.mnemonic = "and"; 359 | inst.imm = reader->getUInt32(); 360 | inst.immBytes = 4; 361 | inst.immSrc = true; 362 | 363 | // Dst is always %eax. 364 | inst.dstReg = 0; 365 | inst.dstRegSet = true; 366 | 367 | addResult(inst, pos, result); 368 | } 369 | 370 | // SUB (r/m16/32 r16/32) 371 | else if (ch == 0x29 && peek) { 372 | inst.mnemonic = "sub"; 373 | processModRegRM(inst); 374 | inst.reverse(); 375 | addResult(inst, pos, result); 376 | } 377 | 378 | // SUB (eAX imm16/32) 379 | else if (ch == 0x2D) { 380 | inst.mnemonic = "sub"; 381 | inst.imm = reader->getUInt32(); 382 | inst.immBytes = 4; 383 | inst.immSrc = true; 384 | 385 | // Dst is always %eax. 386 | inst.dstReg = 0; 387 | inst.dstRegSet = true; 388 | 389 | addResult(inst, pos, result); 390 | } 391 | 392 | // XOR (r/m16/32/64 r16/32/64) 393 | else if (ch == 0x31 && peek) { 394 | inst.mnemonic = "xor"; 395 | processModRegRM(inst); 396 | addResult(inst, pos, result); 397 | } 398 | 399 | // CMP (r16/32 r/m16/32) 400 | else if (ch == 0x3B && peek) { 401 | inst.mnemonic = "cmp"; 402 | processModRegRM(inst); 403 | addResult(inst, pos, result); 404 | } 405 | 406 | // CMP (eAX imm16/32) 407 | else if (ch == 0x3D) { 408 | inst.mnemonic = "cmp"; 409 | inst.imm = reader->getUInt32(); 410 | inst.immBytes = 4; 411 | inst.immSrc = true; 412 | 413 | // Dst is always %eax. 414 | inst.dstReg = 0; 415 | inst.dstRegSet = true; 416 | 417 | addResult(inst, pos, result); 418 | } 419 | 420 | // PUSH (r16/32) 421 | else if (ch >= 0x50 && ch <= 0x57) { 422 | inst.mnemonic = "push"; 423 | if (_64) inst.dataType = DataType::Quadword; 424 | inst.srcReg = getR(ch); 425 | inst.srcRegSet = true; 426 | addResult(inst, pos, result); 427 | } 428 | 429 | // POP (r16/32) 430 | else if (ch >= 0x58 && ch <= 0x5F) { 431 | inst.mnemonic = "pop"; 432 | if (_64) inst.dataType = DataType::Quadword; 433 | inst.srcReg = getR(ch); 434 | inst.srcRegSet = true; 435 | addResult(inst, pos, result); 436 | } 437 | 438 | // MOVSXD (r32/64 r/m32) 439 | // Move with Sign-Extension 440 | else if (ch == 0x63 && peek) { 441 | inst.mnemonic = "movsl"; 442 | processModRegRM(inst); 443 | addResult(inst, pos, result); 444 | } 445 | 446 | // INS (m8 DX) or INSB (m8 DX) 447 | else if (ch == 0x6C && peek) { 448 | inst.mnemonic = "ins"; 449 | inst.dataType = DataType::Byte; 450 | processModRegRM(inst); 451 | 452 | // Src is always %dx/dl 453 | inst.srcReg = 2; 454 | inst.srcRegType = RegType::R8; 455 | inst.srcRegSet = true; 456 | 457 | addResult(inst, pos, result); 458 | } 459 | 460 | // JNZ (rel8) or JNE (rel8) 461 | // Short jump 462 | else if (ch == 0x75 && peek) { 463 | inst.mnemonic = "jne"; 464 | inst.disp = pos - (255 - (int) reader->getUChar()) + 1; 465 | inst.dispBytes = 1; 466 | inst.dispDst = true; 467 | inst.offset = funcAddr; 468 | inst.dataType = DataType::None; 469 | addResult(inst, pos, result); 470 | } 471 | 472 | // ADD, OR, ADC, SBB, AND, SUB, XOR, CMP 473 | // (r/m8 imm8) 474 | else if (ch == 0x80 && peek) { 475 | inst.dataType = DataType::Byte; 476 | inst.srcRegType = inst.dstRegType = RegType::R8; 477 | processModRegRM(inst, true); 478 | 479 | inst.immSrc = true; 480 | processImm8(inst); 481 | 482 | // Don't display the 'dst' after the 'src'. 483 | inst.dstRegSet = false; 484 | 485 | if (inst.dstReg == 0) { 486 | inst.mnemonic = "add"; 487 | } 488 | else if (inst.dstReg == 1) { 489 | inst.mnemonic = "or"; 490 | } 491 | else if (inst.dstReg == 2) { 492 | inst.mnemonic = "adc"; // Add with carry 493 | } 494 | else if (inst.dstReg == 3) { 495 | inst.mnemonic = "sbb"; // Integer subtraction with borrow 496 | } 497 | else if (inst.dstReg == 4) { 498 | inst.mnemonic = "and"; 499 | } 500 | else if (inst.dstReg == 5) { 501 | inst.mnemonic = "sub"; 502 | } 503 | else if (inst.dstReg == 6) { 504 | inst.mnemonic = "xor"; 505 | } 506 | else if (inst.dstReg == 7) { 507 | inst.mnemonic = "cmp"; 508 | } 509 | 510 | addResult(inst, pos, result); 511 | } 512 | 513 | // ADD, OR, ADC, SBB, AND, SUB, XOR, CMP 514 | // (r/m16/32 imm16/32) 515 | else if (ch == 0x81 && peek) { 516 | processModRegRM(inst, true); 517 | 518 | inst.immSrc = true; 519 | processImm32(inst); 520 | 521 | // Don't display the 'dst' after the 'src'. 522 | inst.dstRegSet = false; 523 | 524 | if (inst.dstReg == 0) { 525 | inst.mnemonic = "add"; 526 | } 527 | else if (inst.dstReg == 1) { 528 | inst.mnemonic = "or"; 529 | } 530 | else if (inst.dstReg == 2) { 531 | inst.mnemonic = "adc"; // Add with carry 532 | } 533 | else if (inst.dstReg == 3) { 534 | inst.mnemonic = "sbb"; // Integer subtraction with borrow 535 | } 536 | else if (inst.dstReg == 4) { 537 | inst.mnemonic = "and"; 538 | } 539 | else if (inst.dstReg == 5) { 540 | inst.mnemonic = "sub"; 541 | } 542 | else if (inst.dstReg == 6) { 543 | inst.mnemonic = "xor"; 544 | } 545 | else if (inst.dstReg == 7) { 546 | inst.mnemonic = "cmp"; 547 | } 548 | 549 | addResult(inst, pos, result); 550 | } 551 | 552 | // ADD, OR, ADC, SBB, AND, SUB, XOR, CMP 553 | // (r/m16/32 imm8) 554 | else if (ch == 0x83 && peek) { 555 | processModRegRM(inst, true); 556 | 557 | inst.immSrc = true; 558 | processImm8(inst); 559 | 560 | // Don't display the 'dst' after the 'src'. 561 | inst.dstRegSet = false; 562 | 563 | if (inst.dstReg == 0) { 564 | inst.mnemonic = "add"; 565 | } 566 | else if (inst.dstReg == 1) { 567 | inst.mnemonic = "or"; 568 | } 569 | else if (inst.dstReg == 2) { 570 | inst.mnemonic = "adc"; // Add with carry 571 | } 572 | else if (inst.dstReg == 3) { 573 | inst.mnemonic = "sbb"; // Integer subtraction with borrow 574 | } 575 | else if (inst.dstReg == 4) { 576 | inst.mnemonic = "and"; 577 | } 578 | else if (inst.dstReg == 5) { 579 | inst.mnemonic = "sub"; 580 | } 581 | else if (inst.dstReg == 6) { 582 | inst.mnemonic = "xor"; 583 | } 584 | else if (inst.dstReg == 7) { 585 | inst.mnemonic = "cmp"; 586 | } 587 | 588 | addResult(inst, pos, result); 589 | } 590 | 591 | // TEST (r/m16/32 r16/32) 592 | else if (ch == 0x85 && peek) { 593 | inst.mnemonic = "test"; 594 | processModRegRM(inst); 595 | inst.reverse(); 596 | addResult(inst, pos, result); 597 | } 598 | 599 | // MOV (r/m8 r8) (reverse of 0x8A) 600 | else if (ch == 0x88 && peek) { 601 | inst.mnemonic = "mov"; 602 | inst.dataType = DataType::Byte; 603 | inst.srcRegType = inst.dstRegType = RegType::R8; 604 | processModRegRM(inst); 605 | inst.reverse(); 606 | addResult(inst, pos, result); 607 | } 608 | 609 | // MOV (r8 r/m8) 610 | else if (ch == 0x8A && peek) { 611 | inst.mnemonic = "mov"; 612 | inst.dataType = DataType::Byte; 613 | inst.srcRegType = inst.dstRegType = RegType::R8; 614 | processModRegRM(inst); 615 | addResult(inst, pos, result); 616 | } 617 | 618 | // MOV (r16/32 r/m16/32) 619 | else if (ch == 0x8B && peek) { 620 | inst.mnemonic = "mov"; 621 | processModRegRM(inst); 622 | addResult(inst, pos, result); 623 | } 624 | 625 | // LEA (r16/32 m) Load Effective Address 626 | else if (ch == 0x8D && peek) { 627 | inst.mnemonic = "lea"; 628 | processModRegRM(inst); 629 | addResult(inst, pos, result); 630 | } 631 | 632 | // MOV (r/m16/32 r16/32) (reverse of 0x8B) 633 | else if (ch == 0x89 && peek) { 634 | inst.mnemonic = "mov"; 635 | processModRegRM(inst); 636 | inst.reverse(); 637 | addResult(inst, pos, result); 638 | } 639 | 640 | // NOP 641 | else if (ch == 0x90) { 642 | addResult("nop", pos, result); 643 | } 644 | 645 | // TEST (AL imm8) 646 | else if (ch == 0xA8 && peek) { 647 | inst.mnemonic = "testb"; 648 | inst.disp = reader->getUChar(); 649 | inst.dispBytes = 1; 650 | inst.dispSrc = true; 651 | 652 | // Dst is always %al. 653 | inst.dstReg = 0; 654 | inst.dstRegSet = true; 655 | inst.dstRegType = RegType::R8; 656 | inst.srcRegType = RegType::R8; 657 | 658 | addResult(inst, pos, result); 659 | } 660 | 661 | // MOV (r8 imm8) 662 | else if (ch >= 0xB0 && ch <= 0xB7) { 663 | inst.mnemonic = "mov"; 664 | inst.dataType = DataType::Byte; 665 | inst.srcReg = getR(ch); 666 | inst.srcRegType = RegType::R8; 667 | inst.srcRegSet = true; 668 | 669 | inst.immSrc = true; 670 | processImm8(inst); 671 | 672 | addResult(inst, pos, result); 673 | } 674 | 675 | // MOV (r16/32 imm16/32) 676 | else if (ch >= 0xB8 && ch <= 0xBF) { 677 | inst.mnemonic = "mov"; 678 | inst.srcReg = getR(ch); 679 | inst.srcRegSet = true; 680 | 681 | inst.immSrc = true; 682 | if (inst.dataType == DataType::Quadword) { 683 | processImm64(inst); 684 | } 685 | else { 686 | processImm32(inst); 687 | } 688 | 689 | addResult(inst, pos, result); 690 | } 691 | 692 | // ROL, ROR, RCL, RCR, SHL/SAL, SHR, SAL/SHL, SAR 693 | // (r/m16/32 imm8) 694 | else if (ch == 0xC1) { 695 | processModRegRM(inst, true); 696 | 697 | inst.immSrc = true; 698 | processImm8(inst); 699 | 700 | // Don't display the 'dst' after the 'src'. 701 | inst.dstRegSet = false; 702 | 703 | if (inst.dstReg == 0) { 704 | inst.mnemonic = "rol"; 705 | } 706 | else if (inst.dstReg == 1) { 707 | inst.mnemonic = "ror"; 708 | } 709 | else if (inst.dstReg == 2) { 710 | inst.mnemonic = "rcl"; 711 | } 712 | else if (inst.dstReg == 3) { 713 | inst.mnemonic = "rcr"; 714 | } 715 | else if (inst.dstReg == 4) { 716 | inst.mnemonic = "shl"; 717 | } 718 | else if (inst.dstReg == 5) { 719 | inst.mnemonic = "shr"; 720 | } 721 | else if (inst.dstReg == 6) { 722 | inst.mnemonic = "sal"; 723 | } 724 | else if (inst.dstReg == 7) { 725 | inst.mnemonic = "sar"; 726 | } 727 | 728 | addResult(inst, pos, result); 729 | } 730 | 731 | // RETN 732 | else if (ch == 0xC3) { 733 | addResult("ret", pos, result); 734 | } 735 | 736 | // MOV (r/m8 imm8) 737 | else if (ch == 0xC6 && peek) { 738 | inst.mnemonic = "mov"; 739 | inst.dataType = DataType::Byte; 740 | inst.dstRegType = RegType::R8; 741 | processModRegRM(inst); 742 | inst.reverse(); 743 | 744 | inst.immSrc = true; 745 | inst.srcRegSet = false; 746 | processImm8(inst); 747 | 748 | addResult(inst, pos, result); 749 | } 750 | 751 | // MOV (r/m16/32 imm16/32) 752 | else if (ch == 0xC7 && peek) { 753 | inst.mnemonic = "mov"; 754 | processModRegRM(inst); 755 | inst.reverse(); 756 | 757 | inst.immSrc = true; 758 | inst.srcRegSet = false; 759 | processImm32(inst); 760 | 761 | addResult(inst, pos, result); 762 | } 763 | 764 | // Call (relative function address) 765 | else if (ch == 0xE8) { 766 | inst.mnemonic = "call"; 767 | inst.disp = reader->getUInt32(); 768 | inst.dispBytes = 4; 769 | inst.dispDst = true; 770 | inst.offset = funcAddr + reader->pos(); 771 | inst.call = true; 772 | if (_64) inst.dataType = DataType::Quadword; 773 | addResult(inst, pos, result); 774 | } 775 | 776 | // JMP (rel16/32) (relative address) 777 | else if (ch == 0xE9) { 778 | inst.mnemonic = "jmp"; 779 | inst.dataType = DataType::None; 780 | inst.disp = reader->getUInt32(); 781 | inst.dispBytes = 4; 782 | inst.dispDst = true; 783 | inst.offset = funcAddr + reader->pos(); 784 | addResult(inst, pos, result); 785 | } 786 | 787 | // JMP (rel8) 788 | else if (ch == 0xEB && peek) { 789 | inst.mnemonic = "jmp"; 790 | inst.dataType = DataType::None; 791 | inst.disp = reader->getUChar(); 792 | inst.dispBytes = 1; 793 | inst.dispDst = true; 794 | inst.offset = funcAddr + reader->pos(); 795 | addResult(inst, pos, result); 796 | } 797 | 798 | // HLT 799 | else if (ch == 0xF4) { 800 | addResult("hlt", pos, result); 801 | } 802 | 803 | // INC, DEC, CALL, CALLF, JMP, JMPF, PUSH 804 | else if (ch == 0xFF && peek) { 805 | processModRegRM(inst, true); 806 | 807 | // Don't display the 'dst' after the 'src'. 808 | inst.dstRegSet = false; 809 | 810 | if (inst.dstReg == 0) { 811 | inst.mnemonic = "inc"; 812 | inst.dataType = DataType::None; 813 | } 814 | else if (inst.dstReg == 1) { 815 | inst.mnemonic = "dec"; 816 | inst.dataType = DataType::None; 817 | } 818 | else if (inst.dstReg == 2) { 819 | inst.mnemonic = "call *"; 820 | inst.call = true; 821 | inst.dataType = DataType::None; 822 | } 823 | else if (inst.dstReg == 3) { 824 | inst.mnemonic = "callf"; 825 | inst.call = true; 826 | inst.dataType = DataType::None; 827 | } 828 | else if (inst.dstReg == 4) { 829 | inst.mnemonic = "jmp *"; 830 | inst.dataType = DataType::None; 831 | } 832 | else if (inst.dstReg == 5) { 833 | inst.mnemonic = "jmpf"; 834 | inst.dataType = DataType::None; 835 | } 836 | else if (inst.dstReg == 6) { 837 | inst.mnemonic = "push"; 838 | if (_64) inst.dataType = DataType::Quadword; 839 | } 840 | 841 | addResult(inst, pos, result); 842 | } 843 | 844 | // Two-byte instructions. 845 | else if (ch == 0x0F && peek) { 846 | ch = nch; 847 | reader->getUChar(); // eat 848 | 849 | nch = reader->peekUChar(&peek); 850 | 851 | // NOP (r/m16/32) 852 | if (ch == 0x1F && peek) { 853 | inst.mnemonic = "nop"; 854 | processModRegRM(inst); 855 | inst.dstRegSet = false; 856 | addResult(inst, pos, result); 857 | } 858 | 859 | // JNB (rel16/32), JAE (rel16/32), or JNC (rel16/32). 860 | else if (ch == 0x83) { 861 | inst.mnemonic = "jae"; 862 | inst.disp = reader->getUInt32(); 863 | inst.dispBytes = 4; 864 | inst.dispDst = true; 865 | inst.offset = funcAddr + reader->pos(); 866 | inst.dataType = DataType::None; 867 | addResult(inst, pos, result); 868 | } 869 | 870 | // JZ (rel16/32) or JE (rel16/32), same functionality different 871 | // name. Relative function address. 872 | else if (ch == 0x84) { 873 | inst.mnemonic = "je"; 874 | inst.disp = reader->getUInt32(); 875 | inst.dispBytes = 4; 876 | inst.dispDst = true; 877 | inst.offset = funcAddr + reader->pos(); 878 | inst.dataType = DataType::None; 879 | addResult(inst, pos, result); 880 | } 881 | 882 | // JNZ (rel16/32) or JNE (rel16/32), same functionality 883 | // different name. Relative function address. 884 | else if (ch == 0x85) { 885 | inst.mnemonic = "jne"; 886 | inst.disp = reader->getUInt32(); 887 | inst.dispBytes = 4; 888 | inst.dispDst = true; 889 | inst.offset = funcAddr + reader->pos(); 890 | inst.dataType = DataType::None; 891 | addResult(inst, pos, result); 892 | } 893 | 894 | // JA (rel16/32) or JNBE (rel16/32) 895 | else if (ch == 0x87) { 896 | inst.mnemonic = "ja"; 897 | inst.disp = reader->getUInt32(); 898 | inst.dispBytes = 4; 899 | inst.dispDst = true; 900 | inst.offset = funcAddr + reader->pos(); 901 | inst.dataType = DataType::None; 902 | addResult(inst, pos, result); 903 | } 904 | 905 | // JNL (rel16/32) or JGE (rel16/32). 906 | else if (ch == 0x8D) { 907 | inst.mnemonic = "jge"; 908 | inst.disp = reader->getUInt32(); 909 | inst.dispBytes = 4; 910 | inst.dispDst = true; 911 | inst.offset = funcAddr + reader->pos(); 912 | inst.dataType = DataType::None; 913 | addResult(inst, pos, result); 914 | } 915 | 916 | // JLE (rel16/32) or JNG (rel16/32), same functionality 917 | // different name. Relative function address. 918 | else if (ch == 0x8E) { 919 | inst.mnemonic = "jle"; 920 | inst.disp = reader->getUInt32(); 921 | inst.dispBytes = 4; 922 | inst.dispDst = true; 923 | inst.offset = funcAddr + reader->pos(); 924 | inst.dataType = DataType::None; 925 | addResult(inst, pos, result); 926 | } 927 | 928 | // JNLE (rel16/32) or JG (rel16/32). 929 | else if (ch == 0x8F) { 930 | inst.mnemonic = "jg"; 931 | inst.disp = reader->getUInt32(); 932 | inst.dispBytes = 4; 933 | inst.dispDst = true; 934 | inst.offset = funcAddr + reader->pos(); 935 | inst.dataType = DataType::None; 936 | addResult(inst, pos, result); 937 | } 938 | 939 | // SETZ (r/m8) or SETE (r/m8) 940 | else if (ch == 0x94 && peek) { 941 | inst.mnemonic = "sete"; 942 | inst.srcRegType = RegType::R8; 943 | processModRegRM(inst); 944 | inst.dstRegSet = false; 945 | addResult(inst, pos, result); 946 | } 947 | 948 | // SETNZ (r/m8) or SETNE (r/m8). 949 | else if (ch == 0x95 && peek) { 950 | inst.mnemonic = "setne"; 951 | inst.srcRegType = RegType::R8; 952 | processModRegRM(inst); 953 | inst.dstRegSet = false; 954 | addResult(inst, pos, result); 955 | } 956 | 957 | // MOVZX (r16/32 r/m8) 958 | // Move with Zero-Extension 959 | else if (ch == 0xB6) { 960 | inst.mnemonic = "movzb"; 961 | processModRegRM(inst); 962 | addResult(inst, pos, result); 963 | } 964 | 965 | // MOVSX (r16/32 r/m8) 966 | // Move with Sign-Extension 967 | else if (ch == 0xBE && peek) { 968 | inst.mnemonic = "movsb"; 969 | processModRegRM(inst); 970 | addResult(inst, pos, result); 971 | } 972 | } 973 | 974 | // Unsupported 975 | else { 976 | addResult("Unsupported: " + QString::number(ch, 16).toUpper(), 977 | pos, result); 978 | } 979 | } 980 | 981 | return !result.asmLines.isEmpty(); 982 | } 983 | 984 | bool AsmX86::handleNops(Disassembly &result) { 985 | qint64 pos = reader->pos(); 986 | 987 | // Eat any 0x66's but leave one for the matching beneath. 988 | while (reader->peekList({0x66, 0x66})) { 989 | reader->read(1); 990 | } 991 | 992 | if (reader->peekList({0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00})) { 993 | reader->read(10); 994 | addResult("nopw %cs:0L(%eax,%eax,1)", pos, result); 995 | } 996 | else if (reader->peekList({0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00})) { 997 | reader->read(9); 998 | addResult("nopw 0L(%eax,%eax,1)", pos, result); 999 | } 1000 | else if (reader->peekList({0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00})) { 1001 | reader->read(8); 1002 | addResult("nopl 0L(%eax,%eax,1)", pos, result); 1003 | } 1004 | else if (reader->peekList({0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00})) { 1005 | reader->read(7); 1006 | addResult("nopl 0L(%eax)", pos, result); 1007 | } 1008 | else if (reader->peekList({0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00})) { 1009 | reader->read(6); 1010 | addResult("nopw 0x0(%eax,%eax,1)", pos, result); 1011 | } 1012 | else if (reader->peekList({0x0f, 0x1f, 0x44, 0x00, 0x00})) { 1013 | reader->read(5); 1014 | addResult("nopl 0x0(%eax,%eax,1)", pos, result); 1015 | } 1016 | else if (reader->peekList({0x0f, 0x1f, 0x00})) { 1017 | reader->read(3); 1018 | addResult("nopl 0x0(%eax)", pos, result); 1019 | } 1020 | else if (reader->peekList({0x66, 0x90})) { 1021 | reader->read(2); 1022 | addResult("xchg %ax,%ax", pos, result); 1023 | } 1024 | else { 1025 | return false; 1026 | } 1027 | return true; 1028 | } 1029 | 1030 | void AsmX86::addResult(const Instruction &inst, qint64 pos, 1031 | Disassembly &result) { 1032 | addResult(inst.toString(obj), pos, result); 1033 | } 1034 | 1035 | void AsmX86::addResult(const QString &line, qint64 pos, 1036 | Disassembly &result) { 1037 | result.asmLines << line; 1038 | result.bytesConsumed << reader->pos() - pos; 1039 | } 1040 | 1041 | void AsmX86::splitByte(unsigned char num, unsigned char &mod, unsigned char &op1, 1042 | unsigned char &op2) { 1043 | mod = (num & 0xC0) >> 6; // 2 first bits 1044 | op1 = (num & 0x38) >> 3; // 3 next bits 1045 | op2 = num & 0x7; // 3 last bits 1046 | } 1047 | 1048 | unsigned char AsmX86::getR(unsigned char num) { 1049 | return num & 0x7; // 3 last bits 1050 | } 1051 | 1052 | void AsmX86::splitRex(unsigned char num, bool &w, bool &r, bool &x, bool &b) { 1053 | w = ((num & 0x8) >> 3 == 1); 1054 | r = ((num & 0x4) >> 2 == 1); 1055 | x = ((num & 0x2) >> 1 == 1); 1056 | b = ((num & 0x1) == 1); 1057 | } 1058 | 1059 | void AsmX86::processModRegRM(Instruction &inst, bool noSip) { 1060 | unsigned char ch = reader->getUChar(); 1061 | 1062 | unsigned char mod, op1, op2; 1063 | splitByte(ch, mod, op1, op2); 1064 | 1065 | // [reg] 1066 | if (mod == 0) { 1067 | if (op1 == 4 && !noSip) { 1068 | inst.sipDst = true; 1069 | processSip(inst); 1070 | } 1071 | else if (op1 == 5) { 1072 | inst.dispDst = true; 1073 | inst.dstReg = 16; // RIP/EIP 1074 | inst.dstRegSet = true; 1075 | } 1076 | else { 1077 | inst.dstReg = op1 + (inst.rexR ? 8 : 0); 1078 | inst.dstRegSet = true; 1079 | } 1080 | if (op2 == 4 && !noSip) { 1081 | inst.sipSrc = true; 1082 | processSip(inst); 1083 | } 1084 | else if (op2 == 5) { 1085 | inst.dispSrc = true; 1086 | inst.srcReg = 16; // RIP/EIP 1087 | inst.srcRegSet = true; 1088 | } 1089 | else { 1090 | inst.srcReg = op2 + (inst.rexB ? 8 : 0); 1091 | inst.srcRegSet = true; 1092 | } 1093 | if (inst.dispDst) { 1094 | processDisp32(inst); 1095 | } 1096 | if (inst.dispSrc) { 1097 | processDisp32(inst); 1098 | } 1099 | } 1100 | 1101 | // [reg]+disp8 1102 | else if (mod == 1) { 1103 | if (op1 != 4 || noSip) { 1104 | inst.dstReg = op1 + (inst.rexR ? 8 : 0); 1105 | inst.dstRegSet = true; 1106 | } 1107 | else { 1108 | inst.sipDst = true; 1109 | processSip(inst); 1110 | } 1111 | if (op2 != 4 || noSip) { 1112 | inst.srcReg = op2 + (inst.rexB ? 8 : 0); 1113 | inst.srcRegSet = true; 1114 | } 1115 | else { 1116 | inst.sipSrc = true; 1117 | processSip(inst); 1118 | } 1119 | inst.dispSrc = true; 1120 | processDisp8(inst); 1121 | } 1122 | 1123 | // [reg]+disp32 1124 | else if (mod == 2) { 1125 | if (op1 != 4 || noSip) { 1126 | inst.dstReg = op1 + (inst.rexR ? 8 : 0); 1127 | inst.dstRegSet = true; 1128 | } 1129 | else { 1130 | inst.sipDst = true; 1131 | processSip(inst); 1132 | } 1133 | if (op2 != 4 || noSip) { 1134 | inst.srcReg = op2 + (inst.rexB ? 8 : 0); 1135 | inst.srcRegSet = true; 1136 | } 1137 | else { 1138 | inst.sipSrc = true; 1139 | processSip(inst); 1140 | } 1141 | inst.dispSrc = true; 1142 | processDisp32(inst); 1143 | } 1144 | 1145 | // [reg] 1146 | else if (mod == 3) { 1147 | inst.dstReg = op1 + (inst.rexR ? 8 : 0); 1148 | inst.dstRegSet = true; 1149 | inst.srcReg = op2 + (inst.rexB ? 8 : 0); 1150 | inst.srcRegSet = true; 1151 | } 1152 | 1153 | // Any REX prefix means R8 => R8R. 1154 | if (inst.rexW || inst.rexR || inst.rexX || inst.rexB) { 1155 | if (inst.dstRegSet && inst.dstRegType == RegType::R8) { 1156 | inst.dstRegType = RegType::R8R; 1157 | } 1158 | if (inst.srcRegSet && inst.srcRegType == RegType::R8) { 1159 | inst.srcRegType = RegType::R8R; 1160 | } 1161 | } 1162 | } 1163 | 1164 | void AsmX86::processSip(Instruction &inst) { 1165 | unsigned char sip = reader->getUChar(); 1166 | splitByte(sip, inst.scale, inst.index, inst.base); 1167 | if (inst.rexB) { 1168 | inst.base += 8; 1169 | } 1170 | if (inst.rexX && inst.index != 4) { 1171 | inst.index += 8; 1172 | } 1173 | } 1174 | 1175 | void AsmX86::processDisp8(Instruction &inst) { 1176 | inst.disp = reader->getUChar(); 1177 | inst.dispBytes = 1; 1178 | } 1179 | 1180 | void AsmX86::processDisp32(Instruction &inst) { 1181 | inst.disp = reader->getUInt32(); 1182 | inst.dispBytes = 4; 1183 | } 1184 | 1185 | void AsmX86::processImm8(Instruction &inst) { 1186 | inst.imm = reader->getUChar(); 1187 | inst.immBytes = 1; 1188 | } 1189 | 1190 | void AsmX86::processImm32(Instruction &inst) { 1191 | inst.imm = reader->getUInt32(); 1192 | inst.immBytes = 4; 1193 | } 1194 | 1195 | void AsmX86::processImm64(Instruction &inst) { 1196 | inst.imm = reader->getUInt64(); 1197 | inst.immBytes = 8; 1198 | } 1199 | -------------------------------------------------------------------------------- /src/asm/AsmX86.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_ASM_X86_H 2 | #define BMOD_ASM_X86_H 3 | 4 | #include "Asm.h" 5 | #include "../Reader.h" 6 | #include "../BinaryObject.h" 7 | 8 | namespace { 9 | enum class RegType : int { 10 | R8, // 8-bit register without REX prefix 11 | R8R, // 8-bit register with any REX prefix 12 | R16, // 16-bit register 13 | R32, // 32-bit register 14 | R64, // 64-bit register 15 | MM, // MMX register 16 | XMM, // 128-bit register 17 | SREG // Segment register 18 | }; 19 | 20 | enum class DataType : int { 21 | None, // When it should not be shown, like for CALL and JMP. 22 | Byte, // 8-bit 23 | Word, // 16-bit 24 | Doubleword, // 32-bit 25 | Quadword // 64-bit 26 | }; 27 | 28 | class Instruction { 29 | public: 30 | Instruction() 31 | : dataType{DataType::Doubleword}, srcReg{0}, dstReg{0}, srcRegSet{false}, 32 | dstRegSet{false}, srcRegType{RegType::R32}, dstRegType{RegType::R32}, 33 | scale{0}, index{0}, base{0}, sipSrc{false}, sipDst{false}, disp{0}, 34 | imm{0}, dispSrc{false}, dispDst{false}, immSrc{false}, immDst{false}, 35 | dispBytes{1}, immBytes{1}, offset{0}, call{false}, rexW{false}, 36 | rexR{false}, rexX{false}, rexB{false} 37 | { } 38 | 39 | QString toString(BinaryObjectPtr obj) const; 40 | void reverse(); 41 | 42 | private: 43 | QString getRegString(int reg, RegType type, 44 | RegType type2 = RegType::SREG) const; 45 | QString getSipString(RegType type) const; 46 | QString getDispString() const; 47 | QString getImmString() const; 48 | QString formatHex(quint64 num, int len = 2) const; 49 | 50 | public: 51 | QString mnemonic; 52 | DataType dataType; 53 | unsigned char srcReg, dstReg; 54 | bool srcRegSet, dstRegSet; 55 | RegType srcRegType, dstRegType; 56 | unsigned char scale, index, base; // SIP values 57 | bool sipSrc, sipDst; 58 | quint64 disp, imm; 59 | bool dispSrc, dispDst, immSrc, immDst; 60 | char dispBytes, immBytes; 61 | quint64 offset; 62 | bool call; 63 | bool rexW, rexR, rexX, rexB; 64 | }; 65 | } 66 | 67 | class AsmX86 : public Asm { 68 | public: 69 | AsmX86(BinaryObjectPtr obj); 70 | bool disassemble(SectionPtr sec, Disassembly &result); 71 | 72 | private: 73 | bool handleNops(Disassembly &result); 74 | void addResult(const Instruction &inst, qint64 pos, Disassembly &result); 75 | void addResult(const QString &inst, qint64 pos, Disassembly &result); 76 | 77 | // Split byte into [2][3][3] bits. 78 | void splitByte(unsigned char num, unsigned char &mod, unsigned char &op1, 79 | unsigned char &op2); 80 | 81 | // Get last 3 bits. 82 | unsigned char getR(unsigned char num); 83 | 84 | // Split REX values [0100WRXB]. (64-bit only) 85 | void splitRex(unsigned char num, bool &w, bool &r, bool &x, bool &b); 86 | 87 | void processModRegRM(Instruction &inst, bool noSip = false); 88 | void processSip(Instruction &inst); 89 | void processDisp8(Instruction &inst); 90 | void processDisp32(Instruction &inst); 91 | void processImm8(Instruction &inst); 92 | void processImm32(Instruction &inst); 93 | void processImm64(Instruction &inst); 94 | 95 | BinaryObjectPtr obj; 96 | ReaderPtr reader; 97 | }; 98 | 99 | #endif // BMOD_ASM_X86_H 100 | -------------------------------------------------------------------------------- /src/asm/Disassembler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "Asm.h" 4 | #include "AsmX86.h" 5 | #include "../Util.h" 6 | #include "Disassembler.h" 7 | 8 | Disassembler::Disassembler(BinaryObjectPtr obj) : asm_{nullptr} { 9 | switch (obj->getCpuType()) { 10 | case CpuType::X86: 11 | case CpuType::X86_64: 12 | asm_ = new AsmX86(obj); 13 | break; 14 | 15 | default: break; 16 | } 17 | } 18 | 19 | Disassembler::~Disassembler() { 20 | if (asm_) { 21 | delete asm_; 22 | asm_ = nullptr; 23 | } 24 | } 25 | 26 | bool Disassembler::disassemble(SectionPtr sec, Disassembly &result) { 27 | if (!asm_) return false; 28 | return asm_->disassemble(sec, result); 29 | } 30 | 31 | bool Disassembler::disassemble(const QByteArray &data, Disassembly &result, 32 | quint64 offset) { 33 | int size = data.size(); 34 | auto sec = SectionPtr(new Section(SectionType::Text, "", offset, size)); 35 | sec->setData(data); 36 | return disassemble(sec, result); 37 | } 38 | 39 | bool Disassembler::disassemble(const QString &data, Disassembly &result, 40 | quint64 offset) { 41 | QByteArray input = 42 | Util::hexToData(data.simplified().trimmed().replace(" ", "")); 43 | return disassemble(input, result, offset); 44 | } 45 | -------------------------------------------------------------------------------- /src/asm/Disassembler.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_DISASSEMBLER_H 2 | #define BMOD_DISASSEMBLER_H 3 | 4 | #include 5 | #include 6 | 7 | #include "../BinaryObject.h" 8 | 9 | class Asm; 10 | class QByteArray; 11 | 12 | struct Disassembly { 13 | // Machine code to assembly language. 14 | QStringList asmLines; 15 | 16 | // Bytes consumed per ASM line produced. 17 | QList bytesConsumed; 18 | }; 19 | 20 | class Disassembler { 21 | public: 22 | Disassembler(BinaryObjectPtr obj); 23 | ~Disassembler(); 24 | 25 | bool disassemble(SectionPtr sec, Disassembly &result); 26 | bool disassemble(const QByteArray &data, Disassembly &result, 27 | quint64 offset = 0); 28 | bool disassemble(const QString &data, Disassembly &result, 29 | quint64 offset = 0); 30 | 31 | private: 32 | Asm *asm_; 33 | }; 34 | 35 | #endif // BMOD_DISASSEMBLER_H 36 | -------------------------------------------------------------------------------- /src/formats/Format.cpp: -------------------------------------------------------------------------------- 1 | #include "MachO.h" 2 | #include "Format.h" 3 | 4 | FormatPtr Format::detect(const QString &file) { 5 | // Mach-O 6 | FormatPtr res(new MachO(file)); 7 | if (res->detect()) { 8 | return res; 9 | } 10 | 11 | return nullptr; 12 | } 13 | -------------------------------------------------------------------------------- /src/formats/Format.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_FORMAT_H 2 | #define BMOD_FORMAT_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "FormatType.h" 10 | #include "../Section.h" 11 | #include "../CpuType.h" 12 | #include "../FileType.h" 13 | #include "../BinaryObject.h" 14 | 15 | class Format; 16 | typedef std::shared_ptr FormatPtr; 17 | 18 | class Format { 19 | public: 20 | Format(FormatType type) : type{type} { } 21 | 22 | FormatType getType() const { return type; } 23 | 24 | virtual QString getFile() const =0; 25 | 26 | /** 27 | * Detect whether the magic code of the file corresponds to the 28 | * format. Only reads the first chunk of the file and not all of it! 29 | */ 30 | virtual bool detect() =0; 31 | 32 | /** 33 | * Parses the file into the various sections and so on. 34 | */ 35 | virtual bool parse() =0; 36 | 37 | /** 38 | * Get the list of probed binary objects of the file. 39 | */ 40 | virtual QList getObjects() const =0; 41 | 42 | /** 43 | * Try each of the known formats and see if any of them are 44 | * compatible with the file. 45 | */ 46 | static FormatPtr detect(const QString &file); 47 | 48 | private: 49 | FormatType type; 50 | }; 51 | 52 | #endif // BMOD_FORMAT_H 53 | -------------------------------------------------------------------------------- /src/formats/FormatType.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_FORMAT_TYPE_H 2 | #define BMOD_FORMAT_TYPE_H 3 | 4 | enum class FormatType { 5 | MachO 6 | }; 7 | 8 | #endif // BMOD_FORMAT_TYPE_H 9 | -------------------------------------------------------------------------------- /src/formats/MachO.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "MachO.h" 7 | #include "../Util.h" 8 | #include "../Reader.h" 9 | 10 | MachO::MachO(const QString &file) : Format(FormatType::MachO), file{file} { } 11 | 12 | bool MachO::detect() { 13 | QFile f{file}; 14 | if (!f.open(QIODevice::ReadOnly)) { 15 | return false; 16 | } 17 | 18 | Reader r(f); 19 | bool ok; 20 | quint32 magic = r.getUInt32(&ok); 21 | if (!ok) return false; 22 | 23 | return magic == 0xFEEDFACE || // 32-bit little endian 24 | magic == 0xFEEDFACF || // 64-bit little endian 25 | magic == 0xECAFDEEF || // 32-bit big endian 26 | magic == 0xFCAFDEEF || // 64-bit big endian 27 | magic == 0xCAFEBABE || // Universal binary little endian 28 | magic == 0xBEBAFECA; // Universal binary big endian 29 | } 30 | 31 | bool MachO::parse() { 32 | QFile f{file}; 33 | if (!f.open(QIODevice::ReadOnly)) { 34 | return false; 35 | } 36 | 37 | Reader r(f); 38 | bool ok; 39 | quint32 magic = r.getUInt32(&ok); 40 | if (!ok) return false; 41 | 42 | // Check if this is a universal "fat" binary. 43 | if (magic == 0xCAFEBABE || magic == 0xBEBAFECA) { 44 | // Values are saved as big-endian so read as such. 45 | r.setLittleEndian(false); 46 | 47 | quint32 nfat_arch = r.getUInt32(&ok); 48 | if (!ok) return false; 49 | 50 | // Read "fat" headers. 51 | typedef QPair puu; 52 | QList archs; 53 | for (quint32 i = 0; i < nfat_arch; i++) { 54 | // CPU type. 55 | r.getUInt32(&ok); 56 | if (!ok) return false; 57 | 58 | // CPU sub type. 59 | r.getUInt32(&ok); 60 | if (!ok) return false; 61 | 62 | // File offset to this object file. 63 | quint32 offset = r.getUInt32(&ok); 64 | if (!ok) return false; 65 | 66 | // Size of this object file. 67 | quint32 size = r.getUInt32(&ok); 68 | if (!ok) return false; 69 | 70 | // Alignment as a power of 2. 71 | r.getUInt32(&ok); 72 | if (!ok) return false; 73 | 74 | archs << puu(offset, size); 75 | } 76 | 77 | // Parse the actual binary objects. 78 | foreach (const auto &arch, archs) { 79 | if (!parseHeader(arch.first, arch.second, r)) { 80 | return false; 81 | } 82 | } 83 | } 84 | 85 | // Otherwise, just parse a single object file. 86 | else { 87 | return parseHeader(0, 0, r); 88 | } 89 | 90 | return true; 91 | } 92 | 93 | bool MachO::parseHeader(quint32 offset, quint32 size, Reader &r) { 94 | BinaryObjectPtr binaryObject(new BinaryObject); 95 | 96 | r.seek(offset); 97 | r.setLittleEndian(true); 98 | 99 | bool ok; 100 | quint32 magic = r.getUInt32(&ok); 101 | if (!ok) return false; 102 | 103 | int systemBits{32}; 104 | bool littleEndian{true}; 105 | if (magic == 0xFEEDFACE) { 106 | systemBits = 32; 107 | littleEndian = true; 108 | } 109 | else if (magic == 0xFEEDFACF) { 110 | systemBits = 64; 111 | littleEndian = true; 112 | } 113 | else if (magic == 0xECAFDEEF) { 114 | systemBits = 32; 115 | littleEndian = false; 116 | } 117 | else if (magic == 0xFCAFDEEF) { 118 | systemBits = 64; 119 | littleEndian = false; 120 | } 121 | 122 | binaryObject->setSystemBits(systemBits); 123 | binaryObject->setLittleEndian(littleEndian); 124 | 125 | // Read info in the endianness of the file. 126 | r.setLittleEndian(littleEndian); 127 | 128 | quint32 cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags; 129 | 130 | cputype = r.getUInt32(&ok); 131 | if (!ok) return false; 132 | 133 | cpusubtype = r.getUInt32(&ok); 134 | if (!ok) return false; 135 | 136 | filetype = r.getUInt32(&ok); 137 | if (!ok) return false; 138 | 139 | ncmds = r.getUInt32(&ok); 140 | if (!ok) return false; 141 | 142 | sizeofcmds = r.getUInt32(&ok); 143 | if (!ok) return false; 144 | 145 | flags = r.getUInt32(&ok); 146 | if (!ok) return false; 147 | 148 | // Read reserved field. 149 | if (systemBits == 64) { 150 | r.getUInt32(); 151 | } 152 | 153 | // Types in /usr/local/mach/machine.h 154 | CpuType cpuType{CpuType::X86}; 155 | if (cputype == 7) { // CPU_TYPE_X86, CPU_TYPE_I386 156 | cpuType = CpuType::X86; 157 | } 158 | else if (cputype == 7 + 0x01000000) { // CPU_TYPE_X86 | CPU_ARCH_ABI64 159 | cpuType = CpuType::X86_64; 160 | } 161 | else if (cputype == 11) { // CPU_TYPE_HPPA 162 | cpuType = CpuType::HPPA; 163 | } 164 | else if (cputype == 12) { // CPU_TYPE_ARM 165 | cpuType = CpuType::ARM; 166 | } 167 | else if (cputype == 14) { // CPU_TYPE_SPARC 168 | cpuType = CpuType::SPARC; 169 | } 170 | else if (cputype == 15) { // CPU_TYPE_I860 171 | cpuType = CpuType::I860; 172 | } 173 | else if (cputype == 18) { // CPU_TYPE_POWERPC 174 | cpuType = CpuType::PowerPC; 175 | } 176 | else if (cputype == 18 + 0x01000000) { // CPU_TYPE_POWERPC | CPU_ARCH_ABI64 177 | cpuType = CpuType::PowerPC_64; 178 | } 179 | 180 | binaryObject->setCpuType(cpuType); 181 | 182 | // Subtract 64-bit mask. 183 | if (systemBits == 64) { 184 | cpusubtype -= 0x80000000; 185 | } 186 | 187 | CpuType cpuSubType{CpuType::I386}; 188 | if (cpusubtype == 3) { // CPU_SUBTYPE_386 189 | cpuSubType = CpuType::I386; 190 | } 191 | else if (cpusubtype == 4) { // CPU_SUBTYPE_486 192 | cpuSubType = CpuType::I486; 193 | } 194 | else if (cpusubtype == 4 + (8 << 4)) { // CPU_SUBTYPE_486SX 195 | cpuSubType = CpuType::I486_SX; 196 | } 197 | else if (cpusubtype == 5) { // CPU_SUBTYPE_PENT 198 | cpuSubType = CpuType::Pentium; 199 | } 200 | else if (cpusubtype == 6 + (1 << 4)) { // CPU_SUBTYPE_PENTPRO 201 | cpuSubType = CpuType::PentiumPro; 202 | } 203 | else if (cpusubtype == 6 + (3 << 4)) { // CPU_SUBTYPE_PENTII_M3 204 | cpuSubType = CpuType::PentiumII_M3; 205 | } 206 | else if (cpusubtype == 6 + (5 << 4)) { // CPU_SUBTYPE_PENTII_M5 207 | cpuSubType = CpuType::PentiumII_M5; 208 | } 209 | else if (cpusubtype == 7 + (6 << 4)) { // CPU_SUBTYPE_CELERON 210 | cpuSubType = CpuType::Celeron; 211 | } 212 | else if (cpusubtype == 7 + (7 << 4)) { // CPU_SUBTYPE_CELERON_MOBILE 213 | cpuSubType = CpuType::CeleronMobile; 214 | } 215 | else if (cpusubtype == 8) { // CPU_SUBTYPE_PENTIUM_3 216 | cpuSubType = CpuType::Pentium_3; 217 | } 218 | else if (cpusubtype == 8 + (1 << 4)) { // CPU_SUBTYPE_PENTIUM_3_M 219 | cpuSubType = CpuType::Pentium_3_M; 220 | } 221 | else if (cpusubtype == 8 + (2 << 4)) { // CPU_SUBTYPE_PENTIUM_3_XEON 222 | cpuSubType = CpuType::Pentium_3_Xeon; 223 | } 224 | else if (cpusubtype == 9) { // CPU_SUBTYPE_PENTIUM_M 225 | cpuSubType = CpuType::Pentium_M; 226 | } 227 | else if (cpusubtype == 10) { // CPU_SUBTYPE_PENTIUM_4 228 | cpuSubType = CpuType::Pentium_4; 229 | } 230 | else if (cpusubtype == 10 + (1 << 4)) { // CPU_SUBTYPE_PENTIUM_4_M 231 | cpuSubType = CpuType::Pentium_4_M; 232 | } 233 | else if (cpusubtype == 11) { // CPU_SUBTYPE_ITANIUM 234 | cpuSubType = CpuType::Itanium; 235 | } 236 | else if (cpusubtype == 11 + (1 << 4)) { // CPU_SUBTYPE_ITANIUM_2 237 | cpuSubType = CpuType::Itanium_2; 238 | } 239 | else if (cpusubtype == 12) { // CPU_SUBTYPE_XEON 240 | cpuSubType = CpuType::Xeon; 241 | } 242 | else if (cpusubtype == 12 + (1 << 4)) { // CPU_SUBTYPE_XEON_MP 243 | cpuSubType = CpuType::Xeon_MP; 244 | } 245 | 246 | binaryObject->setCpuSubType(cpuSubType); 247 | 248 | FileType fileType{FileType::Object}; 249 | if (filetype == 1) { // MH_OBJECT 250 | fileType = FileType::Object; 251 | } 252 | else if (filetype == 2) { // MH_EXECUTE 253 | fileType = FileType::Execute; 254 | } 255 | else if (filetype == 4) { // MH_CORE 256 | fileType = FileType::Core; 257 | } 258 | else if (filetype == 5) { // MH_PRELOAD 259 | fileType = FileType::Preload; 260 | } 261 | else if (filetype == 6) { // MH_DYLIB 262 | fileType = FileType::Dylib; 263 | } 264 | else if (filetype == 7) { // MH_DYLINKER 265 | fileType = FileType::Dylinker; 266 | } 267 | else if (filetype == 8) { // MH_BUNDLE 268 | fileType = FileType::Bundle; 269 | } 270 | 271 | binaryObject->setFileType(fileType); 272 | 273 | // TODO: Load flags when necessary. 274 | 275 | // Symbol table offset and number of elements in it. 276 | quint32 symoff{0}, symnum{0}; 277 | 278 | // Dynamic (indirect) symbol table offset and number of elements in 279 | // it. 280 | quint32 indirsymoff{0}, indirsymnum{0}; 281 | 282 | // Parse load commands sequentially. Each consists of the type, size 283 | // and data. 284 | for (int i = 0; i < ncmds; i++) { 285 | quint32 type = r.getUInt32(&ok); 286 | if (!ok) return false; 287 | 288 | quint32 cmdsize = r.getUInt32(&ok); 289 | if (!ok) return false; 290 | 291 | // LC_SEGMENT or LC_SEGMENT_64 292 | if (type == 1 || type == 25) { 293 | QString name{r.read(16)}; 294 | 295 | // Memory address of this segment. 296 | quint64 vmaddr; 297 | if (systemBits == 32) { 298 | vmaddr = r.getUInt32(&ok); 299 | if (!ok) return false; 300 | } 301 | else { 302 | vmaddr = r.getUInt64(&ok); 303 | if (!ok) return false; 304 | } 305 | 306 | // Memory size of this segment. 307 | quint64 vmsize; 308 | if (systemBits == 32) { 309 | vmsize = r.getUInt32(&ok); 310 | if (!ok) return false; 311 | } 312 | else { 313 | vmsize = r.getUInt64(&ok); 314 | if (!ok) return false; 315 | } 316 | 317 | // File offset of this segment. 318 | quint64 fileoff; 319 | if (systemBits == 32) { 320 | fileoff = r.getUInt32(&ok); 321 | if (!ok) return false; 322 | } 323 | else { 324 | fileoff = r.getUInt64(&ok); 325 | if (!ok) return false; 326 | } 327 | 328 | // Amount to map from the file. 329 | quint64 filesize; 330 | if (systemBits == 32) { 331 | filesize = r.getUInt32(&ok); 332 | if (!ok) return false; 333 | } 334 | else { 335 | filesize = r.getUInt64(&ok); 336 | if (!ok) return false; 337 | } 338 | 339 | // Maximum VM protection. 340 | r.getUInt32(&ok); 341 | if (!ok) return false; 342 | 343 | // Initial VM protection. 344 | r.getUInt32(&ok); 345 | if (!ok) return false; 346 | 347 | // Number of sections in segment. 348 | quint32 nsects = r.getUInt32(&ok); 349 | if (!ok) return false; 350 | 351 | // Flags. 352 | r.getUInt32(&ok); 353 | if (!ok) return false; 354 | 355 | // Read sections. 356 | if (nsects > 0) { 357 | for (int j = 0; j < nsects; j++) { 358 | QString secname{r.read(16)}; 359 | 360 | QString segname{r.read(16)}; 361 | 362 | // Memory address of this section. 363 | quint64 addr; 364 | if (systemBits == 32) { 365 | addr = r.getUInt32(&ok); 366 | if (!ok) return false; 367 | } 368 | else { 369 | addr = r.getUInt64(&ok); 370 | if (!ok) return false; 371 | } 372 | 373 | // Size in bytes of this section. 374 | quint64 secsize; 375 | if (systemBits == 32) { 376 | secsize = r.getUInt32(&ok); 377 | if (!ok) return false; 378 | } 379 | else { 380 | secsize = r.getUInt64(&ok); 381 | if (!ok) return false; 382 | } 383 | 384 | // File offset of this section. 385 | quint32 secfileoff = r.getUInt32(&ok); 386 | if (!ok) return false; 387 | 388 | // Section alignment (power of 2). 389 | r.getUInt32(&ok); 390 | if (!ok) return false; 391 | 392 | // File offset of relocation entries. 393 | r.getUInt32(&ok); 394 | if (!ok) return false; 395 | 396 | // Number of relocation entries. 397 | r.getUInt32(&ok); 398 | if (!ok) return false; 399 | 400 | // Flags. 401 | r.getUInt32(&ok); 402 | if (!ok) return false; 403 | 404 | // Reserved fields. 405 | r.getUInt32(); 406 | r.getUInt32(); 407 | if (systemBits == 64) { 408 | r.getUInt32(); 409 | } 410 | 411 | // Store needed sections. 412 | if (segname == "__TEXT") { 413 | if (secname == "__text") { 414 | SectionPtr sec(new Section(SectionType::Text, 415 | QObject::tr("Program"), 416 | addr, secsize, offset + secfileoff)); 417 | binaryObject->addSection(sec); 418 | } 419 | else if (secname == "__symbol_stub" || 420 | secname == "__stubs") { 421 | SectionPtr sec(new Section(SectionType::SymbolStubs, 422 | QObject::tr("Symbol Stubs"), 423 | addr, secsize, offset + secfileoff)); 424 | binaryObject->addSection(sec); 425 | } 426 | else if (secname == "__cstring") { 427 | SectionPtr sec(new Section(SectionType::CString, 428 | QObject::tr("C-Strings"), 429 | addr, secsize, offset + secfileoff)); 430 | binaryObject->addSection(sec); 431 | } 432 | else if (secname == "__objc_methname") { 433 | SectionPtr sec(new Section(SectionType::CString, 434 | QObject::tr("ObjC Method Names"), 435 | addr, secsize, offset + secfileoff)); 436 | binaryObject->addSection(sec); 437 | } 438 | } 439 | 440 | } 441 | } 442 | } 443 | 444 | // LC_DYLD_INFO or LC_DYLD_INFO_ONLY 445 | else if (type == 0x22 || type == (0x22 | 0x80000000)) { 446 | // File offset to rebase info. 447 | r.getUInt32(&ok); 448 | if (!ok) return false; 449 | 450 | // Size of rebase info. 451 | r.getUInt32(&ok); 452 | if (!ok) return false; 453 | 454 | // File offset to binding info. 455 | r.getUInt32(&ok); 456 | if (!ok) return false; 457 | 458 | // Size of binding info. 459 | r.getUInt32(&ok); 460 | if (!ok) return false; 461 | 462 | // File offset to weak binding info. 463 | r.getUInt32(&ok); 464 | if (!ok) return false; 465 | 466 | // Size of weak binding info. 467 | r.getUInt32(&ok); 468 | if (!ok) return false; 469 | 470 | // File offset to lazy binding info. 471 | r.getUInt32(&ok); 472 | if (!ok) return false; 473 | 474 | // Size of lazy binding info. 475 | r.getUInt32(&ok); 476 | if (!ok) return false; 477 | 478 | // File offset to export info. 479 | r.getUInt32(&ok); 480 | if (!ok) return false; 481 | 482 | // Size of export info. 483 | r.getUInt32(&ok); 484 | if (!ok) return false; 485 | } 486 | 487 | // LC_SYMTAB 488 | else if (type == 2) { 489 | // Symbol table offset. 490 | symoff = r.getUInt32(&ok); 491 | if (!ok) return false; 492 | 493 | // Number of symbol table entries. 494 | symnum = r.getUInt32(&ok); 495 | if (!ok) return false; 496 | 497 | // String table offset. 498 | quint32 stroff = r.getUInt32(&ok); 499 | if (!ok) return false; 500 | 501 | // String table size in bytes. 502 | quint32 strsize = r.getUInt32(&ok); 503 | if (!ok) return false; 504 | 505 | SectionPtr sec(new Section(SectionType::String, 506 | QObject::tr("String Table"), 507 | stroff, strsize, offset + stroff)); 508 | binaryObject->addSection(sec); 509 | } 510 | 511 | // LC_DYSYMTAB 512 | else if (type == 0xB) { 513 | // Index to local symbols. 514 | r.getUInt32(&ok); 515 | if (!ok) return false; 516 | 517 | // Number of local symbols. 518 | r.getUInt32(&ok); 519 | if (!ok) return false; 520 | 521 | // Index to externally defined symbols. 522 | r.getUInt32(&ok); 523 | if (!ok) return false; 524 | 525 | // Number of externally defined symbols. 526 | r.getUInt32(&ok); 527 | if (!ok) return false; 528 | 529 | // Index to undefined defined symbols. 530 | r.getUInt32(&ok); 531 | if (!ok) return false; 532 | 533 | // Number of undefined defined symbols. 534 | r.getUInt32(&ok); 535 | if (!ok) return false; 536 | 537 | // File offset to table of contents. 538 | r.getUInt32(&ok); 539 | if (!ok) return false; 540 | 541 | // Number of entries in the table of contents. 542 | r.getUInt32(&ok); 543 | if (!ok) return false; 544 | 545 | // File offset to module table. 546 | r.getUInt32(&ok); 547 | if (!ok) return false; 548 | 549 | // Number of module table entries. 550 | r.getUInt32(&ok); 551 | if (!ok) return false; 552 | 553 | // File offset to referenced symbol table. 554 | r.getUInt32(&ok); 555 | if (!ok) return false; 556 | 557 | // Number of referenced symbol table entries. 558 | r.getUInt32(&ok); 559 | if (!ok) return false; 560 | 561 | // File offset to indirect symbol table. 562 | indirsymoff = r.getUInt32(&ok); 563 | if (!ok) return false; 564 | 565 | // Number of indirect symbol table entries. 566 | indirsymnum = r.getUInt32(&ok); 567 | if (!ok) return false; 568 | 569 | // File offset to external relocation entries. 570 | r.getUInt32(&ok); 571 | if (!ok) return false; 572 | 573 | // Number of external relocation entries. 574 | r.getUInt32(&ok); 575 | if (!ok) return false; 576 | 577 | // File offset to local relocation entries. 578 | r.getUInt32(&ok); 579 | if (!ok) return false; 580 | 581 | // Number of local relocation entries. 582 | r.getUInt32(&ok); 583 | if (!ok) return false; 584 | } 585 | 586 | // LC_LOAD_DYLIB, LC_ID_DYLIB or LC_LOAD_WEAK_DYLIB 587 | else if (type == 0xC || type == 0xD || type == 0x18 + 0x80000000) { 588 | // Library path name offset. 589 | quint32 liboffset = r.getUInt32(&ok); 590 | if (!ok) return false; 591 | 592 | // Time stamp. 593 | r.getUInt32(&ok); 594 | if (!ok) return false; 595 | 596 | // Current version. 597 | r.getUInt32(&ok); 598 | if (!ok) return false; 599 | 600 | // Compatibility version. 601 | r.getUInt32(&ok); 602 | if (!ok) return false; 603 | 604 | // Library path name. 605 | r.read(cmdsize - liboffset); 606 | } 607 | 608 | // LC_LOAD_DYLINKER or LC_DYLD_ENVIRONMENT 609 | else if (type == 0xE || type == 0x27) { 610 | // Dynamic linker's path name. 611 | quint32 noffset = r.getUInt32(&ok); 612 | if (!ok) return false; 613 | 614 | r.read(cmdsize - noffset); 615 | } 616 | 617 | // LC_UUID 618 | else if (type == 0x1B) { 619 | r.read(16); 620 | } 621 | 622 | // LC_VERSION_MIN_MACOSX 623 | else if (type == 0x24) { 624 | // Version (X.Y.Z is encoded in nibbles xxxx.yy.zz) 625 | r.getUInt32(&ok); 626 | if (!ok) return false; 627 | 628 | // SDK version (X.Y.Z is encoded in nibbles xxxx.yy.zz) 629 | r.getUInt32(&ok); 630 | if (!ok) return false; 631 | } 632 | 633 | // LC_SOURCE_VERSION 634 | else if (type == 0x2A) { 635 | // Version (A.B.C.D.E packed as a24.b10.c10.d10.e10) 636 | r.getUInt64(&ok); 637 | if (!ok) return false; 638 | } 639 | 640 | // LC_MAIN 641 | else if (type == (0x28 | 0x80000000)) { 642 | // File (__TEXT) offset of main() 643 | r.getUInt64(&ok); 644 | if (!ok) return false; 645 | 646 | // Initial stack size if not zero. 647 | r.getUInt64(&ok); 648 | if (!ok) return false; 649 | } 650 | 651 | // LC_FUNCTION_STARTS, LC_DYLIB_CODE_SIGN_DRS, 652 | // LC_SEGMENT_SPLIT_INFO or LC_CODE_SIGNATURE 653 | else if (type == 0x26 || type == 0x2B || type == 0x1E || type == 0x1D) { 654 | // File offset to data in __LINKEDIT segment. 655 | quint32 off = r.getUInt32(&ok); 656 | if (!ok) return false; 657 | 658 | // File size of data in __LINKEDIT segment. 659 | quint32 siz = r.getUInt32(&ok); 660 | if (!ok) return false; 661 | 662 | // LC_FUNCTION_STARTS 663 | if (type == 0x26) { 664 | SectionPtr sec(new Section(SectionType::FuncStarts, 665 | QObject::tr("Function Starts"), 666 | off, siz, offset + off)); 667 | binaryObject->addSection(sec); 668 | } 669 | 670 | // LC_CODE_SIGNATURE 671 | else if (type == 0x1D) { 672 | SectionPtr sec(new Section(SectionType::CodeSig, 673 | QObject::tr("Code Signature"), 674 | off, siz, offset + off)); 675 | binaryObject->addSection(sec); 676 | } 677 | } 678 | 679 | // LC_DATA_IN_CODE 680 | else if (type == 0x29) { 681 | // From mach_header to start of data range. 682 | r.getUInt32(&ok); 683 | if (!ok) return false; 684 | 685 | // Number of bytes in data range. 686 | r.getUInt16(&ok); 687 | if (!ok) return false; 688 | 689 | // Dice kind value. 690 | r.getUInt16(&ok); 691 | if (!ok) return false; 692 | } 693 | 694 | // LC_THREAD or LC_UNIXTHREAD 695 | else if (type == 0x4 || type == 0x5) { 696 | quint32 flavor = r.getUInt32(&ok); 697 | if (!ok) return false; 698 | 699 | quint32 count = r.getUInt32(&ok); 700 | if (!ok) return false; 701 | 702 | // Data. 703 | r.read(flavor * count); 704 | } 705 | 706 | // LC_RPATH 707 | else if (type == 0x1C + 0x80000000) { 708 | // Name offset. 709 | quint32 off = r.getUInt32(&ok); 710 | if (!ok) return false; 711 | 712 | // Name. 713 | r.read(cmdsize - off); 714 | } 715 | 716 | // Temporary: Fail if unknown! 717 | else { 718 | qDebug() << "what is type:" << type; 719 | exit(0); 720 | } 721 | } 722 | 723 | // Parse symbol table if found. 724 | // (/usr/include/macho/nlist.h) 725 | quint32 symsize{0}; 726 | SymbolTable symTable; 727 | if (symnum > 0) { 728 | r.seek(symoff); 729 | qint64 pos; 730 | for (int i = 0; i < symnum; i++) { 731 | pos = r.pos(); 732 | 733 | // Index into the string table. 734 | quint32 index = r.getUInt32(&ok); 735 | if (!ok) return false; 736 | 737 | // Type flag. 738 | r.getUChar(&ok); 739 | if (!ok) return false; 740 | 741 | // Section number or NO_SECT. 742 | r.getUChar(&ok); 743 | if (!ok) return false; 744 | 745 | // Description. 746 | r.getUInt16(&ok); 747 | if (!ok) return false; 748 | 749 | // Value of the symbol (or stab offset). 750 | quint64 value; 751 | if (systemBits == 32) { 752 | value = r.getUInt32(&ok); 753 | if (!ok) return false; 754 | } 755 | else { 756 | value = r.getUInt64(&ok); 757 | if (!ok) return false; 758 | } 759 | 760 | symTable.addSymbol(SymbolEntry(index, value)); 761 | symsize += (r.pos() - pos); 762 | } 763 | 764 | SectionPtr sec(new Section(SectionType::Symbols, 765 | QObject::tr("Symbol Table"), 766 | symoff, symsize, offset + symoff)); 767 | binaryObject->addSection(sec); 768 | } 769 | 770 | // Parse dynamic symbol table if found. Store the offsets into the 771 | // symbol table for later updating. 772 | quint32 dynsymsize{0}; 773 | SymbolTable dynsymTable; 774 | if (indirsymnum > 0) { 775 | r.seek(indirsymoff); 776 | qint64 pos; 777 | for (int i = 0; i < indirsymnum; i++) { 778 | pos = r.pos(); 779 | 780 | quint32 num = r.getUInt32(&ok); 781 | if (!ok) return false; 782 | 783 | dynsymTable.addSymbol(SymbolEntry(num, 0)); 784 | dynsymsize += (r.pos() - pos); 785 | } 786 | 787 | SectionPtr sec(new Section(SectionType::DynSymbols, 788 | QObject::tr("Dynamic Symbol Table"), 789 | indirsymoff, dynsymsize, offset + indirsymoff)); 790 | binaryObject->addSection(sec); 791 | } 792 | 793 | // Fill data of stored sections. 794 | foreach (auto sec, binaryObject->getSections()) { 795 | r.seek(sec->getOffset()); 796 | sec->setData(r.read(sec->getSize())); 797 | } 798 | 799 | // If symbol table loaded then merge string table entries into it. 800 | if (symnum > 0) { 801 | auto strTable = binaryObject->getSection(SectionType::String); 802 | if (strTable) { 803 | auto &data = strTable->getData(); 804 | auto &symbols = symTable.getSymbols(); 805 | for (int h = 0; h < symbols.size(); h++) { 806 | auto &symbol = symbols[h]; 807 | QByteArray tmp; 808 | for (int i = symbol.getIndex(); i < data.size(); i++) { 809 | char c = data[i]; 810 | tmp += c; 811 | if (c == 0) break; 812 | } 813 | symbol.setString(QString::fromUtf8(tmp)); 814 | } 815 | } 816 | binaryObject->setSymbolTable(symTable); 817 | } 818 | 819 | // If dynamic symbol table loaded then merge data from symbol table 820 | // and symbol stubs into it. 821 | if (indirsymnum > 0 && symnum > 0) { 822 | auto stubs = binaryObject->getSection(SectionType::SymbolStubs); 823 | if (stubs) { 824 | quint64 stubAddr = stubs->getAddress(); 825 | const auto &symbols = symTable.getSymbols(); 826 | auto &dynsymbols = dynsymTable.getSymbols(); 827 | for (int h = 0; h < dynsymbols.size(); h++) { 828 | auto &symbol = dynsymbols[h]; 829 | int idx = symbol.getIndex(); 830 | if (idx >= 0 && idx < symnum) { 831 | // The index corresponds to the index in the symbol table. 832 | symbol.setString(symbols[idx].getString()); 833 | 834 | // Each symbol stub takes up 6 bytes. 835 | symbol.setValue(stubAddr + h * 6); 836 | } 837 | } 838 | } 839 | binaryObject->setDynSymbolTable(dynsymTable); 840 | } 841 | 842 | objects << binaryObject; 843 | return true; 844 | } 845 | -------------------------------------------------------------------------------- /src/formats/MachO.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_MACHO_FORMAT_H 2 | #define BMOD_MACHO_FORMAT_H 3 | 4 | #include "Format.h" 5 | 6 | class Reader; 7 | 8 | class MachO : public Format { 9 | public: 10 | MachO(const QString &file); 11 | 12 | QString getFile() const { return file; } 13 | 14 | bool detect(); 15 | bool parse(); 16 | 17 | QList getObjects() const { return objects; } 18 | 19 | private: 20 | bool parseHeader(quint32 offset, quint32 size, Reader &reader); 21 | 22 | QString file; 23 | QList objects; 24 | }; 25 | 26 | #endif // BMOD_MACHO_FORMAT_H 27 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include // 2 | #include 3 | #include 4 | #include 5 | 6 | #include "Version.h" 7 | #include "widgets/MainWindow.h" 8 | 9 | int main(int argc, char **argv) { 10 | QApplication app(argc, argv); 11 | QCoreApplication::setApplicationName("bmod"); 12 | QCoreApplication::setApplicationVersion(versionString()); 13 | 14 | QStringList files; 15 | for (int i = 1; i < argc; i++) { 16 | files << QString::fromUtf8(argv[i]); 17 | } 18 | 19 | // Start in event loop. 20 | MainWindow main(files); 21 | QTimer::singleShot(0, &main, SLOT(show())); 22 | 23 | return app.exec(); 24 | } 25 | -------------------------------------------------------------------------------- /src/panes/ArchPane.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../Util.h" 6 | #include "ArchPane.h" 7 | 8 | ArchPane::ArchPane(FormatType type, BinaryObjectPtr obj) 9 | : Pane(Kind::Arch), type{type}, obj{obj} 10 | { 11 | createLayout(); 12 | } 13 | 14 | void ArchPane::createLayout() { 15 | auto *gridLayout = new QGridLayout; 16 | gridLayout->setContentsMargins(0, 0, 0, 0); 17 | 18 | gridLayout->addWidget(new QLabel(tr("Format type:")), 0, 0); 19 | gridLayout->addWidget(new QLabel(Util::formatTypeString(type)), 0, 1); 20 | 21 | gridLayout->addWidget(new QLabel(tr("System bits:")), 1, 0); 22 | gridLayout->addWidget(new QLabel(QString::number(obj->getSystemBits())), 23 | 1, 1); 24 | 25 | gridLayout->addWidget(new QLabel(tr("Little endian:")), 2, 0); 26 | gridLayout->addWidget(new QLabel(obj->isLittleEndian() ? 27 | tr("Yes") : tr("No")), 2, 1); 28 | 29 | gridLayout->addWidget(new QLabel(tr("CPU type:")), 3, 0); 30 | gridLayout->addWidget(new QLabel(Util::cpuTypeString(obj->getCpuType())), 31 | 3, 1); 32 | 33 | gridLayout->addWidget(new QLabel(tr("CPU sub type:")), 4, 0); 34 | gridLayout->addWidget(new QLabel(Util::cpuTypeString(obj->getCpuSubType())), 35 | 4, 1); 36 | 37 | gridLayout->addWidget(new QLabel(tr("File type:")), 5, 0); 38 | gridLayout->addWidget(new QLabel(Util::fileTypeString(obj->getFileType())), 39 | 5, 1); 40 | 41 | gridLayout->addWidget(new QLabel(tr("Sections:")), 6, 0); 42 | gridLayout->addWidget(new QLabel(QString::number(obj->getSections().size())), 43 | 6, 1); 44 | 45 | auto *w = new QWidget; 46 | w->setMaximumWidth(300); 47 | w->setLayout(gridLayout); 48 | 49 | auto *layout = new QVBoxLayout; 50 | layout->setContentsMargins(0, 0, 0, 0); 51 | layout->addWidget(w); 52 | layout->addStretch(); 53 | 54 | setLayout(layout); 55 | } 56 | -------------------------------------------------------------------------------- /src/panes/ArchPane.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_ARCH_PANE_H 2 | #define BMOD_ARCH_PANE_H 3 | 4 | #include "Pane.h" 5 | #include "../BinaryObject.h" 6 | #include "../formats/FormatType.h" 7 | 8 | class ArchPane : public Pane { 9 | public: 10 | ArchPane(FormatType type, BinaryObjectPtr obj); 11 | 12 | private: 13 | void createLayout(); 14 | 15 | FormatType type; 16 | BinaryObjectPtr obj; 17 | }; 18 | 19 | #endif // BMOD_ARCH_PANE_H 20 | -------------------------------------------------------------------------------- /src/panes/DisassemblyPane.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../Util.h" 13 | #include "DisassemblyPane.h" 14 | #include "../asm/Disassembler.h" 15 | #include "../widgets/TreeWidget.h" 16 | 17 | namespace { 18 | class ItemDelegate : public QStyledItemDelegate { 19 | public: 20 | ItemDelegate(DisassemblyPane *pane, QTreeWidget *tree, BinaryObjectPtr obj, 21 | SectionPtr sec) 22 | : pane{pane}, tree{tree}, obj{obj}, sec{sec} 23 | { } 24 | 25 | QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, 26 | const QModelIndex &index) const { 27 | int col = index.column(); 28 | if (col != 1) { 29 | return nullptr; 30 | } 31 | 32 | QString text = index.data().toString().trimmed(); 33 | if (text.isEmpty()) { 34 | return nullptr; 35 | } 36 | 37 | QString mask; 38 | int blocks = text.split(" ").size(); 39 | for (int i = 0; i < blocks; i++) { 40 | mask += "HH "; 41 | } 42 | if (mask.endsWith(" ")) { 43 | mask.chop(1); 44 | } 45 | 46 | auto *edit = new QLineEdit(parent); 47 | edit->setInputMask(mask); 48 | edit->setText(text); 49 | return edit; 50 | } 51 | 52 | void setModelData(QWidget *editor, QAbstractItemModel *model, 53 | const QModelIndex &index) const { 54 | auto *edit = qobject_cast(editor); 55 | if (edit) { 56 | QString oldStr = model->data(index).toString().trimmed(); 57 | QString newStr = edit->text().toUpper(); 58 | if (newStr == oldStr) { 59 | return; 60 | } 61 | model->setData(index, newStr); 62 | auto *item = tree->topLevelItem(index.row()); 63 | if (item) { 64 | Util::setTreeItemMarked(item, index.column()); 65 | 66 | // Change region. 67 | quint64 addr = item->text(0).toULongLong(nullptr, 16); 68 | quint64 pos = addr - sec->getAddress(); 69 | QByteArray data = Util::hexToData(newStr.replace(" ", "")); 70 | sec->setSubData(data, pos); 71 | 72 | // Update disassembly. 73 | auto tmpSec = 74 | SectionPtr(new Section(SectionType::Text, QString(), addr, pos)); 75 | tmpSec->setData(data); 76 | 77 | Disassembler dis(obj); 78 | Disassembly result; 79 | if (dis.disassemble(tmpSec, result)) { 80 | item->setText(2, result.asmLines.join(" ")); 81 | if (result.asmLines.size() > 1) { 82 | pane->showUpdateButton(); 83 | QMessageBox::information(nullptr, "bmod", 84 | tr("Changes implied new code lines.") + "\n" + 85 | tr("Disassemble again for clear representation.")); 86 | } 87 | } 88 | else { 89 | item->setText(2, tr("Could not disassemble!")); 90 | } 91 | 92 | emit pane->modified(); 93 | } 94 | } 95 | } 96 | 97 | private: 98 | DisassemblyPane *pane; 99 | QTreeWidget *tree; 100 | BinaryObjectPtr obj; 101 | SectionPtr sec; 102 | }; 103 | } 104 | 105 | DisassemblyPane::DisassemblyPane(BinaryObjectPtr obj, SectionPtr sec) 106 | : Pane(Kind::Disassembly), obj{obj}, sec{sec}, shown{false} 107 | { 108 | createLayout(); 109 | } 110 | 111 | void DisassemblyPane::showUpdateButton() { 112 | updateBtn->show(); 113 | } 114 | 115 | void DisassemblyPane::showEvent(QShowEvent *event) { 116 | QWidget::showEvent(event); 117 | if (!shown) { 118 | shown = true; 119 | setup(); 120 | } 121 | else if (sec->isModified()) { 122 | QDateTime mod = sec->modifiedWhen(); 123 | if (secModified.isNull() || mod != secModified) { 124 | secModified = mod; 125 | setup(); 126 | } 127 | } 128 | } 129 | 130 | void DisassemblyPane::onUpdateClicked() { 131 | setup(); 132 | } 133 | 134 | void DisassemblyPane::createLayout() { 135 | label = new QLabel; 136 | 137 | updateBtn = new QPushButton(tr("Update disassembly")); 138 | updateBtn->hide(); 139 | connect(updateBtn, &QPushButton::clicked, 140 | this, &DisassemblyPane::onUpdateClicked); 141 | 142 | auto *topLayout = new QHBoxLayout; 143 | topLayout->setContentsMargins(0, 0, 0, 0); 144 | topLayout->addWidget(label); 145 | topLayout->addStretch(); 146 | topLayout->addWidget(updateBtn); 147 | 148 | treeWidget = new TreeWidget; 149 | treeWidget->setHeaderLabels(QStringList{tr("Address"), tr("Data"), tr("Disassembly")}); 150 | treeWidget->setColumnWidth(0, obj->getSystemBits() == 64 ? 110 : 70); 151 | treeWidget->setColumnWidth(1, 200); 152 | treeWidget->setColumnWidth(2, 200); 153 | treeWidget->setItemDelegate(new ItemDelegate(this, treeWidget, obj, sec)); 154 | treeWidget->setMachineCodeColumns(QList{1}); 155 | treeWidget->setCpuType(obj->getCpuType()); 156 | treeWidget->setAddressColumn(0); 157 | 158 | auto *layout = new QVBoxLayout; 159 | layout->setContentsMargins(0, 0, 0, 0); 160 | layout->addLayout(topLayout); 161 | layout->addWidget(treeWidget); 162 | 163 | setLayout(layout); 164 | } 165 | 166 | void DisassemblyPane::setup() { 167 | updateBtn->hide(); 168 | treeWidget->clear(); 169 | 170 | QProgressDialog progDiag(this); 171 | progDiag.setLabelText(tr("Disassembling data..")); 172 | progDiag.setCancelButton(nullptr); 173 | progDiag.setRange(0, 100); 174 | progDiag.show(); 175 | qApp->processEvents(); 176 | 177 | quint64 addr = sec->getAddress(); 178 | quint32 pos = 0, size = sec->getSize(); 179 | const QByteArray &data = sec->getData(); 180 | 181 | const auto &symTable = obj->getSymbolTable(); 182 | 183 | Disassembler dis(obj); 184 | Disassembly result; 185 | if (dis.disassemble(sec, result)) { 186 | int len = result.asmLines.size(); 187 | label->setText(tr("%1 instructions").arg(len)); 188 | for (int i = 0; i < len; i++) { 189 | const QString &line = result.asmLines[i]; 190 | short bytes = result.bytesConsumed[i]; 191 | 192 | // Check if this is the beginning of a function. 193 | QString funcName; 194 | if (symTable.getString(addr, funcName)) { 195 | auto *item = new QTreeWidgetItem; 196 | item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); 197 | int col{2}; 198 | item->setText(col, funcName); 199 | auto font = item->font(col); 200 | font.setBold(true); 201 | item->setFont(col, font); 202 | if (i > 0) { 203 | treeWidget->addTopLevelItem(new QTreeWidgetItem); 204 | } 205 | treeWidget->addTopLevelItem(item); 206 | } 207 | 208 | auto *item = new QTreeWidgetItem; 209 | item->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); 210 | item->setText(0, Util::padString(QString::number(addr, 16).toUpper(), 211 | obj->getSystemBits() / 8)); 212 | 213 | QString code; 214 | for (int j = pos; j < pos + bytes && j < size; j++) { 215 | QString hex = 216 | Util::padString(QString::number((unsigned char) data[j], 16), 2); 217 | code += hex + " "; 218 | } 219 | if (code.endsWith(" ")) { 220 | code.chop(1); 221 | } 222 | code = code.toUpper(); 223 | item->setText(1, code); 224 | 225 | item->setText(2, line); 226 | treeWidget->addTopLevelItem(item); 227 | 228 | addr += bytes; 229 | pos += bytes; 230 | 231 | static int lastPerc{0}; 232 | int perc = (long double) pos / (long double) size * 100.0; 233 | if (perc > lastPerc || perc == 100) { 234 | lastPerc = perc; 235 | progDiag.setValue(perc); 236 | progDiag.setLabelText(tr("Disassembling data.. %1% (%2 of %3)") 237 | .arg(perc) 238 | .arg(Util::formatSize(pos)) 239 | .arg(Util::formatSize(size))); 240 | qApp->processEvents(); 241 | } 242 | } 243 | 244 | // Mark items as modified if a region states it. 245 | const auto &modRegs = sec->getModifiedRegions(); 246 | int rows = treeWidget->topLevelItemCount(); 247 | quint64 offset = 248 | treeWidget->topLevelItem(0)->text(0).toULongLong(nullptr, 16); 249 | for (int row = 0; row < rows; row++) { 250 | auto *item = treeWidget->topLevelItem(row); 251 | addr = item->text(0).toULongLong(nullptr, 16) - offset; 252 | int size = item->text(1).split(" ", QString::SkipEmptyParts).size(); 253 | foreach (const auto ®, modRegs) { 254 | if (reg.first >= addr && reg.first < addr + size) { 255 | Util::setTreeItemMarked(item, 1); 256 | int excess = (reg.first + reg.second) - (addr + size); 257 | if (excess > 0) { 258 | for (int row2 = row + 1; row2 < rows; row2++) { 259 | auto *item2 = treeWidget->topLevelItem(row2); 260 | if (item2) { 261 | int size2 = item2->text(1).split(" ", QString::SkipEmptyParts).size(); 262 | Util::setTreeItemMarked(item2, 1); 263 | excess -= size2; 264 | if (excess <= 0) break; 265 | } 266 | else break; 267 | } 268 | } 269 | } 270 | } 271 | } 272 | 273 | treeWidget->setFocus(); 274 | } 275 | else { 276 | label->setText(tr("Could not disassemble machine code!")); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/panes/DisassemblyPane.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_DISASSEMBLY_PANE_H 2 | #define BMOD_DISASSEMBLY_PANE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "Pane.h" 8 | #include "../Section.h" 9 | #include "../BinaryObject.h" 10 | 11 | class QLabel; 12 | class TreeWidget; 13 | class QPushButton; 14 | 15 | class DisassemblyPane : public Pane { 16 | Q_OBJECT 17 | 18 | public: 19 | DisassemblyPane(BinaryObjectPtr obj, SectionPtr sec); 20 | 21 | void showUpdateButton(); 22 | 23 | protected: 24 | void showEvent(QShowEvent *event); 25 | 26 | private slots: 27 | void onUpdateClicked(); 28 | 29 | private: 30 | void createLayout(); 31 | void setup(); 32 | void setItemMarked(QTreeWidgetItem *item, int column); 33 | 34 | BinaryObjectPtr obj; 35 | SectionPtr sec; 36 | QDateTime secModified; 37 | 38 | bool shown; 39 | QLabel *label; 40 | QPushButton *updateBtn; 41 | TreeWidget *treeWidget; 42 | }; 43 | 44 | #endif // BMOD_DISASSEMBLY_PANE_H 45 | -------------------------------------------------------------------------------- /src/panes/GenericPane.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "GenericPane.h" 4 | #include "../widgets/MachineCodeWidget.h" 5 | 6 | GenericPane::GenericPane(BinaryObjectPtr obj, SectionPtr sec) 7 | : Pane(Kind::Generic), obj{obj}, sec{sec} 8 | { 9 | createLayout(); 10 | } 11 | 12 | void GenericPane::createLayout() { 13 | auto *codeWidget = new MachineCodeWidget(obj, sec); 14 | connect(codeWidget, SIGNAL(modified()), this, SIGNAL(modified())); 15 | 16 | auto *layout = new QVBoxLayout; 17 | layout->setContentsMargins(0, 0, 0, 0); 18 | layout->addWidget(codeWidget); 19 | 20 | setLayout(layout); 21 | } 22 | -------------------------------------------------------------------------------- /src/panes/GenericPane.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_GENERIC_PANE_H 2 | #define BMOD_GENERIC_PANE_H 3 | 4 | #include "Pane.h" 5 | #include "../Section.h" 6 | #include "../BinaryObject.h" 7 | 8 | class GenericPane : public Pane { 9 | public: 10 | GenericPane(BinaryObjectPtr obj, SectionPtr sec); 11 | 12 | private: 13 | void createLayout(); 14 | 15 | BinaryObjectPtr obj; 16 | SectionPtr sec; 17 | }; 18 | 19 | #endif // BMOD_GENERIC_PANE_H 20 | -------------------------------------------------------------------------------- /src/panes/Pane.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_PANE_H 2 | #define BMOD_PANE_H 3 | 4 | #include 5 | 6 | class Pane : public QWidget { 7 | Q_OBJECT 8 | 9 | public: 10 | enum class Kind { 11 | Arch, 12 | Program, 13 | Disassembly, 14 | Strings, 15 | Symbols, 16 | Generic 17 | }; 18 | 19 | signals: 20 | void modified(); 21 | 22 | protected: 23 | Pane(Kind kind) : kind{kind} { } 24 | 25 | public: 26 | Kind getKind() const { return kind; } 27 | 28 | private: 29 | Kind kind; 30 | }; 31 | 32 | #endif // BMOD_PANE_H 33 | -------------------------------------------------------------------------------- /src/panes/ProgramPane.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ProgramPane.h" 4 | #include "../widgets/MachineCodeWidget.h" 5 | 6 | ProgramPane::ProgramPane(BinaryObjectPtr obj, SectionPtr sec) 7 | : Pane(Kind::Program), obj{obj}, sec{sec} 8 | { 9 | createLayout(); 10 | } 11 | 12 | void ProgramPane::createLayout() { 13 | auto *codeWidget = new MachineCodeWidget(obj, sec); 14 | connect(codeWidget, SIGNAL(modified()), this, SIGNAL(modified())); 15 | 16 | auto *layout = new QVBoxLayout; 17 | layout->setContentsMargins(0, 0, 0, 0); 18 | layout->addWidget(codeWidget); 19 | 20 | setLayout(layout); 21 | } 22 | -------------------------------------------------------------------------------- /src/panes/ProgramPane.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_PROGRAM_PANE_H 2 | #define BMOD_PROGRAM_PANE_H 3 | 4 | #include "Pane.h" 5 | #include "../Section.h" 6 | #include "../BinaryObject.h" 7 | 8 | class ProgramPane : public Pane { 9 | public: 10 | ProgramPane(BinaryObjectPtr obj, SectionPtr sec); 11 | 12 | private: 13 | void createLayout(); 14 | 15 | BinaryObjectPtr obj; 16 | SectionPtr sec; 17 | }; 18 | 19 | #endif // BMOD_PROGRAM_PANE_H 20 | -------------------------------------------------------------------------------- /src/panes/StringsPane.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../Util.h" 11 | #include "StringsPane.h" 12 | #include "../widgets/TreeWidget.h" 13 | 14 | namespace { 15 | class ItemDelegate : public QStyledItemDelegate { 16 | public: 17 | ItemDelegate(StringsPane *pane, QTreeWidget *tree, SectionPtr sec) 18 | : pane{pane}, tree{tree}, sec{sec} 19 | { } 20 | 21 | QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, 22 | const QModelIndex &index) const { 23 | int col = index.column(); 24 | // TODO: Also make it possible to edit the strings directly 25 | if (col != 3) { 26 | return nullptr; 27 | } 28 | 29 | QString text = index.data().toString().trimmed(); 30 | if (text.isEmpty()) { 31 | return nullptr; 32 | } 33 | 34 | QString mask; 35 | int blocks = text.size() / 2 - 1; 36 | for (int i = 0; i < blocks; i++) { 37 | mask += "HH"; 38 | } 39 | 40 | auto *edit = new QLineEdit(parent); 41 | edit->setInputMask(mask); 42 | edit->setText(text); 43 | return edit; 44 | } 45 | 46 | void setModelData(QWidget *editor, QAbstractItemModel *model, 47 | const QModelIndex &index) const { 48 | auto *edit = qobject_cast(editor); 49 | if (edit) { 50 | QString oldStr = model->data(index).toString().trimmed(); 51 | QString newStr = edit->text().toUpper() + "00"; 52 | if (newStr == oldStr) { 53 | return; 54 | } 55 | model->setData(index, newStr); 56 | auto *item = tree->topLevelItem(index.row()); 57 | if (item) { 58 | Util::setTreeItemMarked(item, index.column()); 59 | 60 | // Update string representation. 61 | item->setText(1, Util::hexToString(newStr) 62 | .replace("\n", "\\n") 63 | .replace("\t", "\\t") 64 | .replace("\r", "\\r")); 65 | 66 | // Change region. 67 | quint64 addr = item->text(0).toULongLong(nullptr, 16); 68 | quint64 pos = addr - sec->getAddress(); 69 | QByteArray data = Util::hexToData(newStr); 70 | sec->setSubData(data, pos); 71 | 72 | emit pane->modified(); 73 | } 74 | } 75 | } 76 | 77 | private: 78 | StringsPane *pane; 79 | QTreeWidget *tree; 80 | SectionPtr sec; 81 | }; 82 | } 83 | 84 | StringsPane::StringsPane(BinaryObjectPtr obj, SectionPtr sec) 85 | : Pane(Kind::Strings), obj{obj}, sec{sec}, shown{false} 86 | { 87 | createLayout(); 88 | } 89 | 90 | void StringsPane::showEvent(QShowEvent *event) { 91 | QWidget::showEvent(event); 92 | if (!shown) { 93 | shown = true; 94 | setup(); 95 | } 96 | else if (sec->isModified()) { 97 | QDateTime mod = sec->modifiedWhen(); 98 | if (secModified.isNull() || mod != secModified) { 99 | secModified = mod; 100 | setup(); 101 | } 102 | } 103 | } 104 | 105 | void StringsPane::createLayout() { 106 | label = new QLabel; 107 | 108 | treeWidget = new TreeWidget; 109 | treeWidget->setHeaderLabels(QStringList{tr("Address"), tr("String"), 110 | tr("Length"), tr("Data")}); 111 | treeWidget->setColumnWidth(0, obj->getSystemBits() == 64 ? 110 : 70); 112 | treeWidget->setColumnWidth(1, 200); 113 | treeWidget->setColumnWidth(2, 50); 114 | treeWidget->setColumnWidth(3, 200); 115 | treeWidget->setItemDelegate(new ItemDelegate(this, treeWidget, sec)); 116 | treeWidget->setAddressColumn(0); 117 | 118 | auto *layout = new QVBoxLayout; 119 | layout->setContentsMargins(0, 0, 0, 0); 120 | layout->addWidget(label); 121 | layout->addWidget(treeWidget); 122 | 123 | setLayout(layout); 124 | } 125 | 126 | void StringsPane::setup() { 127 | treeWidget->clear(); 128 | 129 | quint64 addr = sec->getAddress(); 130 | const QByteArray &data = sec->getData(); 131 | if (data.isEmpty()) { 132 | return; 133 | } 134 | 135 | int len = data.size(); 136 | 137 | QProgressDialog progDiag(this); 138 | progDiag.setLabelText(tr("Processing strings..")); 139 | progDiag.setCancelButton(nullptr); 140 | progDiag.setRange(0, 100); 141 | progDiag.show(); 142 | qApp->processEvents(); 143 | 144 | QByteArray cur; 145 | for (int i = 0; i < len; i++) { 146 | char c = data[i]; 147 | cur += c; 148 | if (c == 0) { 149 | auto *item = new QTreeWidgetItem; 150 | item->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); 151 | item->setText(0, Util::padString(QString::number(addr, 16).toUpper(), 152 | obj->getSystemBits() / 8)); 153 | 154 | QString str = QString::fromUtf8(cur); 155 | str = str.replace("\n", "\\n").replace("\t", "\\t").replace("\r", "\\r"); 156 | 157 | item->setText(1, str); 158 | item->setText(2, QString::number(str.size())); 159 | 160 | QString dataStr; 161 | for (int j = 0; j < cur.size(); j++) { 162 | QString hex = 163 | Util::padString(QString::number((unsigned char) cur[j], 16), 2); 164 | dataStr += hex; 165 | } 166 | item->setText(3, dataStr.toUpper()); 167 | 168 | treeWidget->addTopLevelItem(item); 169 | addr += cur.size(); 170 | cur.clear(); 171 | 172 | static int lastPerc{0}; 173 | int perc = (float) i / (float) len * 100.0; 174 | if (perc > lastPerc || perc == 100) { 175 | lastPerc = perc; 176 | progDiag.setValue(perc); 177 | progDiag.setLabelText(tr("Processing strings.. %1% (%2 of %3)") 178 | .arg(perc) 179 | .arg(Util::formatSize(i)) 180 | .arg(Util::formatSize(len))); 181 | qApp->processEvents(); 182 | } 183 | } 184 | } 185 | 186 | // Mark items as modified if a region states it. 187 | const auto &modRegs = sec->getModifiedRegions(); 188 | int rows = treeWidget->topLevelItemCount(); 189 | quint64 offset = 190 | treeWidget->topLevelItem(0)->text(0).toULongLong(nullptr, 16); 191 | for (int row = 0; row < rows; row++) { 192 | auto *item = treeWidget->topLevelItem(row); 193 | addr = item->text(0).toULongLong(nullptr, 16) - offset; 194 | int size = item->text(2).toInt() + 1; // account for \0 195 | foreach (const auto ®, modRegs) { 196 | if (reg.first >= addr && reg.first < addr + size) { 197 | Util::setTreeItemMarked(item, 3); 198 | int excess = (reg.first + reg.second) - (addr + size); 199 | if (excess > 0) { 200 | for (int row2 = row + 1; row2 < rows; row2++) { 201 | auto *item2 = treeWidget->topLevelItem(row2); 202 | if (item2) { 203 | int size2 = item2->text(2).toInt() + 1; // account for \0 204 | Util::setTreeItemMarked(item2, 3); 205 | excess -= size2; 206 | if (excess <= 0) break; 207 | } 208 | else break; 209 | } 210 | } 211 | } 212 | } 213 | } 214 | 215 | int padSize = obj->getSystemBits() / 8; 216 | addr = sec->getAddress(); 217 | label->setText(tr("Section size: %1, address %2 to %3, %4 rows") 218 | .arg(Util::formatSize(len)) 219 | .arg(Util::padString(QString::number(addr, 16).toUpper(), 220 | padSize)) 221 | .arg(Util::padString(QString::number(addr + len, 16).toUpper(), 222 | padSize)) 223 | .arg(treeWidget->topLevelItemCount())); 224 | 225 | treeWidget->setFocus(); 226 | } 227 | -------------------------------------------------------------------------------- /src/panes/StringsPane.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_STRINGS_PANE_H 2 | #define BMOD_STRINGS_PANE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "Pane.h" 8 | #include "../Section.h" 9 | #include "../BinaryObject.h" 10 | 11 | class QLabel; 12 | class TreeWidget; 13 | 14 | class StringsPane : public Pane { 15 | public: 16 | StringsPane(BinaryObjectPtr obj, SectionPtr sec); 17 | 18 | protected: 19 | void showEvent(QShowEvent *event); 20 | 21 | private: 22 | void createLayout(); 23 | void setup(); 24 | void setItemMarked(QTreeWidgetItem *item, int column); 25 | 26 | BinaryObjectPtr obj; 27 | SectionPtr sec; 28 | QDateTime secModified; 29 | 30 | bool shown; 31 | QLabel *label; 32 | TreeWidget *treeWidget; 33 | }; 34 | 35 | #endif // BMOD_STRINGS_PANE_H 36 | -------------------------------------------------------------------------------- /src/panes/SymbolsPane.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../Util.h" 7 | #include "SymbolsPane.h" 8 | #include "../widgets/TreeWidget.h" 9 | 10 | SymbolsPane::SymbolsPane(BinaryObjectPtr obj, SectionPtr sec, Type type) 11 | : Pane(Kind::Symbols), obj{obj}, sec{sec}, type{type}, shown{false} 12 | { 13 | createLayout(); 14 | } 15 | 16 | void SymbolsPane::showEvent(QShowEvent *event) { 17 | QWidget::showEvent(event); 18 | if (!shown) { 19 | shown = true; 20 | setup(); 21 | } 22 | } 23 | 24 | void SymbolsPane::createLayout() { 25 | label = new QLabel; 26 | 27 | treeWidget = new TreeWidget; 28 | treeWidget->setHeaderLabels(QStringList{tr("Index"), tr("Value"), tr("String")}); 29 | treeWidget->setColumnWidth(0, 100); 30 | treeWidget->setColumnWidth(1, 100); 31 | treeWidget->setColumnWidth(2, 200); 32 | 33 | auto *layout = new QVBoxLayout; 34 | layout->setContentsMargins(0, 0, 0, 0); 35 | layout->addWidget(label); 36 | layout->addWidget(treeWidget); 37 | 38 | setLayout(layout); 39 | } 40 | 41 | void SymbolsPane::setup() { 42 | treeWidget->clear(); 43 | 44 | QProgressDialog progDiag(this); 45 | progDiag.setLabelText(tr("Processing symbols..")); 46 | progDiag.setCancelButton(nullptr); 47 | progDiag.setRange(0, 100); 48 | progDiag.show(); 49 | qApp->processEvents(); 50 | 51 | const auto &symTable = 52 | (type == Type::Symbols ? obj->getSymbolTable() 53 | : obj->getDynSymbolTable()); 54 | foreach (const auto &symbol, symTable.getSymbols()) { 55 | auto *item = new QTreeWidgetItem; 56 | item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); 57 | quint32 idx = symbol.getIndex(); 58 | item->setText(0, Util::padString(QString::number(idx, 16).toUpper(), 59 | obj->getSystemBits() / 8)); 60 | item->setText(1, QString::number(symbol.getValue(), 16).toUpper()); 61 | item->setText(2, symbol.getString()); 62 | treeWidget->addTopLevelItem(item); 63 | } 64 | 65 | int padSize = obj->getSystemBits() / 8; 66 | qint64 len = sec->getData().size(); 67 | quint64 addr = sec->getAddress(); 68 | label->setText(tr("Section size: %1, address %2 to %3, %4 rows") 69 | .arg(Util::formatSize(len)) 70 | .arg(Util::padString(QString::number(addr, 16).toUpper(), 71 | padSize)) 72 | .arg(Util::padString(QString::number(addr + len, 16).toUpper(), 73 | padSize)) 74 | .arg(treeWidget->topLevelItemCount())); 75 | } 76 | -------------------------------------------------------------------------------- /src/panes/SymbolsPane.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_SYMBOLS_PANE_H 2 | #define BMOD_SYMBOLS_PANE_H 3 | 4 | #include "Pane.h" 5 | #include "../Section.h" 6 | #include "../BinaryObject.h" 7 | 8 | class QLabel; 9 | class TreeWidget; 10 | 11 | class SymbolsPane : public Pane { 12 | public: 13 | enum class Type { 14 | Symbols, 15 | DynSymbols 16 | }; 17 | 18 | SymbolsPane(BinaryObjectPtr obj, SectionPtr sec, Type type); 19 | 20 | protected: 21 | void showEvent(QShowEvent *event); 22 | 23 | private: 24 | void createLayout(); 25 | void setup(); 26 | 27 | BinaryObjectPtr obj; 28 | SectionPtr sec; 29 | Type type; 30 | 31 | bool shown; 32 | QLabel *label; 33 | TreeWidget *treeWidget; 34 | }; 35 | 36 | #endif // BMOD_SYMBOLS_PANE_H 37 | -------------------------------------------------------------------------------- /src/widgets/BinaryWidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "Util.h" 11 | #include "BinaryWidget.h" 12 | 13 | #include "../panes/Pane.h" 14 | #include "../panes/ArchPane.h" 15 | #include "../panes/ProgramPane.h" 16 | #include "../panes/SymbolsPane.h" 17 | #include "../panes/StringsPane.h" 18 | #include "../panes/GenericPane.h" 19 | #include "../panes/DisassemblyPane.h" 20 | 21 | BinaryWidget::BinaryWidget(FormatPtr fmt) : fmt{fmt} { 22 | createLayout(); 23 | setup(); 24 | } 25 | 26 | void BinaryWidget::commit() { 27 | QFile f(getFile()); 28 | if (!f.open(QIODevice::ReadWrite)) { 29 | QMessageBox::critical(this, "bmod", tr("Could not open file for writing!")); 30 | return; 31 | } 32 | 33 | QProgressDialog progDiag(this); 34 | progDiag.setLabelText(tr("Committing to file..")); 35 | progDiag.setCancelButton(nullptr); 36 | progDiag.setRange(0, 0); 37 | progDiag.show(); 38 | qApp->processEvents(); 39 | 40 | foreach (const auto obj, fmt->getObjects()) { 41 | foreach (const auto sec, obj->getSections()) { 42 | if (!sec->isModified()) { 43 | continue; 44 | } 45 | const QByteArray &data = sec->getData(); 46 | foreach (const auto ®ion, sec->getModifiedRegions()) { 47 | f.seek(sec->getOffset() + region.first); 48 | f.write(data.mid(region.first, region.second)); 49 | } 50 | } 51 | } 52 | } 53 | 54 | void BinaryWidget::createLayout() { 55 | listWidget = new QListWidget; 56 | listWidget->setFixedWidth(175); 57 | connect(listWidget, &QListWidget::currentRowChanged, 58 | this, &BinaryWidget::onModeChanged); 59 | 60 | stackLayout = new QStackedLayout; 61 | stackLayout->setContentsMargins(0, 0, 0, 0); 62 | 63 | auto *layout = new QHBoxLayout; 64 | layout->setContentsMargins(5, 5, 5, 5); 65 | layout->addWidget(listWidget); 66 | layout->addLayout(stackLayout); 67 | 68 | setLayout(layout); 69 | } 70 | 71 | void BinaryWidget::onModeChanged(int row) { 72 | stackLayout->setCurrentIndex(row); 73 | } 74 | 75 | void BinaryWidget::setup() { 76 | foreach (const auto obj, fmt->getObjects()) { 77 | auto *archPane = new ArchPane(fmt->getType(), obj); 78 | QString cpuStr = Util::cpuTypeString(obj->getCpuType()), 79 | cpuSubStr = Util::cpuTypeString(obj->getCpuSubType()); 80 | addPane(tr("%1 (%2)").arg(cpuStr).arg(cpuSubStr), archPane); 81 | 82 | SectionPtr sec = obj->getSection(SectionType::Text); 83 | if (sec) { 84 | addPane(tr("Executable Code"), new ProgramPane(obj, sec), 1); 85 | addPane(tr("Disassembly"), new DisassemblyPane(obj, sec), 2); 86 | } 87 | 88 | sec = obj->getSection(SectionType::SymbolStubs); 89 | if (sec) { 90 | addPane(sec->getName(), new GenericPane(obj, sec), 1); 91 | } 92 | 93 | sec = obj->getSection(SectionType::Symbols); 94 | if (sec) { 95 | addPane(sec->getName(), 96 | new SymbolsPane(obj, sec, SymbolsPane::Type::Symbols), 1); 97 | addPane(tr("Raw View"), new GenericPane(obj, sec), 2); 98 | } 99 | 100 | sec = obj->getSection(SectionType::DynSymbols); 101 | if (sec) { 102 | addPane(sec->getName(), 103 | new SymbolsPane(obj, sec, SymbolsPane::Type::DynSymbols), 1); 104 | addPane(tr("Raw View"), new GenericPane(obj, sec), 2); 105 | } 106 | 107 | sec = obj->getSection(SectionType::String); 108 | if (sec) { 109 | addPane(sec->getName(), new StringsPane(obj, sec), 1); 110 | addPane(tr("Raw View"), new GenericPane(obj, sec), 2); 111 | } 112 | 113 | foreach (auto sec, obj->getSectionsByType(SectionType::CString)) { 114 | addPane(sec->getName(), new StringsPane(obj, sec), 1); 115 | addPane(tr("Raw View"), new GenericPane(obj, sec), 2); 116 | } 117 | 118 | sec = obj->getSection(SectionType::FuncStarts); 119 | if (sec) { 120 | addPane(sec->getName(), new GenericPane(obj, sec), 1); 121 | } 122 | 123 | sec = obj->getSection(SectionType::CodeSig); 124 | if (sec) { 125 | addPane(sec->getName(), new GenericPane(obj, sec), 1); 126 | } 127 | } 128 | 129 | if (listWidget->count() > 0) { 130 | listWidget->setCurrentRow(0); 131 | } 132 | } 133 | 134 | void BinaryWidget::addPane(const QString &title, Pane *pane, int level) { 135 | listWidget->addItem(QString(level * 4, ' ') + title); 136 | stackLayout->addWidget(pane); 137 | connect(pane, SIGNAL(modified()), this, SIGNAL(modified())); 138 | } 139 | -------------------------------------------------------------------------------- /src/widgets/BinaryWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_BINARY_WIDGET_H 2 | #define BMOD_BINARY_WIDGET_H 3 | 4 | #include 5 | 6 | #include "../formats/Format.h" 7 | 8 | class Pane; 9 | class QListWidget; 10 | class QStackedLayout; 11 | 12 | class BinaryWidget : public QWidget { 13 | Q_OBJECT 14 | 15 | public: 16 | BinaryWidget(FormatPtr fmt); 17 | 18 | QString getFile() const { return fmt->getFile(); } 19 | 20 | void commit(); 21 | 22 | signals: 23 | void modified(); 24 | 25 | private slots: 26 | void onModeChanged(int row); 27 | 28 | private: 29 | void createLayout(); 30 | void setup(); 31 | void addPane(const QString &title, Pane *pane, int level = 0); 32 | 33 | FormatPtr fmt; 34 | 35 | QListWidget *listWidget; 36 | QStackedLayout *stackLayout; 37 | }; 38 | 39 | #endif // BMOD_BINARY_WIDGET_H 40 | -------------------------------------------------------------------------------- /src/widgets/ConversionHelper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../Util.h" 12 | #include "ConversionHelper.h" 13 | 14 | ConversionHelper::ConversionHelper(QWidget *parent) : QDialog(parent) { 15 | setWindowTitle(tr("Conversion Helper")); 16 | createLayout(); 17 | resize(300, 550); 18 | } 19 | 20 | void ConversionHelper::onTextEdited(const QString &text) { 21 | QLineEdit *edit = qobject_cast(sender()); 22 | if (!edit) return; 23 | 24 | quint64 val{0}; 25 | bool ok{true}; 26 | 27 | for (int i = 0; i < edits.size(); i++) { 28 | QLineEdit *e = edits[i]; 29 | if (e != edit) continue; 30 | 31 | // Oct 32 | if (i == 0) { 33 | val = text.toULongLong(&ok, 8); 34 | } 35 | 36 | // Dec 37 | else if (i == 1) { 38 | val = text.toULongLong(&ok, 10); 39 | } 40 | 41 | // Hex 42 | else if (i == 2) { 43 | val = text.toULongLong(&ok, 16); 44 | } 45 | 46 | // ACSII 47 | else if (i == 3) { 48 | if (text.size() == 1) { 49 | val = (int) (char) text[0].toLatin1(); 50 | } 51 | } 52 | } 53 | 54 | for (int i = 0; i < edits.size(); i++) { 55 | QLineEdit *e = edits[i]; 56 | if (e == edit) continue; 57 | 58 | if (!ok) { 59 | e->setText(""); 60 | continue; 61 | } 62 | 63 | // Oct 64 | if (i == 0) { 65 | e->setText(QString::number(val, 8)); 66 | } 67 | 68 | // Dec 69 | else if (i == 1) { 70 | e->setText(QString::number(val)); 71 | } 72 | 73 | // Hex 74 | else if (i == 2) { 75 | e->setText(QString::number(val, 16).toUpper()); 76 | } 77 | 78 | // ACSII 79 | else if (i == 3 && val > 0) { 80 | QString s = (val >= 32 && val <= 126 ? QString((char) val) : ""); 81 | e->setText(s); 82 | } 83 | } 84 | } 85 | 86 | void ConversionHelper::onHexToText() { 87 | QString hex = hexEdit->toPlainText().trimmed().replace(" ", ""); 88 | if (hex.isEmpty()) return; 89 | 90 | bool unicode = (encBox->currentIndex() == 1); 91 | 92 | QString text = Util::hexToAscii(hex, 0, hex.size() / 2, unicode); 93 | if (text.isEmpty()) { 94 | QMessageBox::information(this, "bmod", 95 | tr("Could not convert hex to text.")); 96 | return; 97 | } 98 | textEdit->setText(text); 99 | } 100 | 101 | void ConversionHelper::onTextToHex() { 102 | QString text = textEdit->toPlainText().trimmed(); 103 | if (text.isEmpty()) return; 104 | 105 | bool unicode = (encBox->currentIndex() == 1); 106 | 107 | QString hex; 108 | foreach (auto c, text) { 109 | int ic; 110 | if (unicode) { 111 | ic = c.unicode(); 112 | } 113 | else { 114 | ic = c.toLatin1(); 115 | } 116 | hex += Util::padString(QString::number(ic, 16).toUpper(), 2) + " "; 117 | } 118 | if (hex.isEmpty()) { 119 | QMessageBox::information(this, "bmod", 120 | tr("Could not convert text to hex.")); 121 | return; 122 | } 123 | hexEdit->setText(hex); 124 | } 125 | 126 | void ConversionHelper::createLayout() { 127 | auto *numLayout = new QGridLayout; 128 | numLayout->setContentsMargins(5, 5, 5, 5); 129 | numLayout->addWidget(new QLabel(tr("Octal")), 0, 0); 130 | numLayout->addWidget(new QLabel(tr("Decimal")), 1, 0); 131 | numLayout->addWidget(new QLabel(tr("Hexadecimal")), 2, 0); 132 | numLayout->addWidget(new QLabel(tr("ASCII")), 3, 0); 133 | 134 | for (int i = 0; i < 4; i++) { 135 | auto *edit = new QLineEdit; 136 | connect(edit, &QLineEdit::textEdited, 137 | this, &ConversionHelper::onTextEdited); 138 | 139 | if (i == 3) { 140 | edit->setMaxLength(1); 141 | } 142 | 143 | numLayout->addWidget(edit, i, 1); 144 | edits << edit; 145 | } 146 | 147 | auto *numGroup = new QGroupBox(tr("Numbers")); 148 | numGroup->setLayout(numLayout); 149 | 150 | hexEdit = new QTextEdit; 151 | hexEdit->setTabChangesFocus(true); 152 | 153 | textEdit = new QTextEdit; 154 | textEdit->setTabChangesFocus(true); 155 | 156 | encBox = new QComboBox; 157 | encBox->addItem(tr("ASCII"), 0); 158 | encBox->addItem(tr("Unicode"), 1); 159 | 160 | auto *encLayout = new QHBoxLayout; 161 | encLayout->addWidget(new QLabel(tr("Encoding:"))); 162 | encLayout->addWidget(encBox); 163 | encLayout->addStretch(); 164 | 165 | auto *hexToText = new QPushButton(tr("Hex -> Text")); 166 | connect(hexToText, &QPushButton::clicked, 167 | this, &ConversionHelper::onHexToText); 168 | 169 | auto *textToHex = new QPushButton(tr("Text -> Hex")); 170 | connect(textToHex, &QPushButton::clicked, 171 | this, &ConversionHelper::onTextToHex); 172 | 173 | auto *buttonLayout = new QHBoxLayout; 174 | buttonLayout->setContentsMargins(0, 0, 0, 0); 175 | buttonLayout->addStretch(); 176 | buttonLayout->addWidget(hexToText); 177 | buttonLayout->addWidget(textToHex); 178 | buttonLayout->addStretch(); 179 | 180 | auto *textLayout = new QVBoxLayout; 181 | textLayout->setContentsMargins(5, 5, 5, 5); 182 | textLayout->addWidget(new QLabel(tr("Hex strings"))); 183 | textLayout->addWidget(hexEdit); 184 | textLayout->addWidget(new QLabel(tr("Text"))); 185 | textLayout->addWidget(textEdit); 186 | textLayout->addLayout(encLayout); 187 | textLayout->addLayout(buttonLayout); 188 | 189 | auto *textGroup = new QGroupBox(tr("Text")); 190 | textGroup->setLayout(textLayout); 191 | 192 | auto *layout = new QVBoxLayout; 193 | layout->addWidget(numGroup); 194 | layout->addWidget(textGroup); 195 | 196 | setLayout(layout); 197 | } 198 | -------------------------------------------------------------------------------- /src/widgets/ConversionHelper.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_CONVERSION_HELPER_H 2 | #define BMOD_CONVERSION_HELPER_H 3 | 4 | #include 5 | #include 6 | 7 | class QLineEdit; 8 | class QTextEdit; 9 | class QComboBox; 10 | 11 | class ConversionHelper : public QDialog { 12 | Q_OBJECT 13 | 14 | public: 15 | ConversionHelper(QWidget *parent = nullptr); 16 | 17 | private slots: 18 | void onTextEdited(const QString &text); 19 | void onHexToText(); 20 | void onTextToHex(); 21 | 22 | private: 23 | void createLayout(); 24 | 25 | QList edits; 26 | QTextEdit *hexEdit, *textEdit; 27 | QComboBox *encBox; 28 | }; 29 | 30 | #endif // BMOD_CONVERSION_HELPER_H 31 | -------------------------------------------------------------------------------- /src/widgets/DisassemblerDialog.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../Util.h" 13 | #include "../BinaryObject.h" 14 | #include "DisassemblerDialog.h" 15 | #include "../asm/Disassembler.h" 16 | 17 | DisassemblerDialog::DisassemblerDialog(QWidget *parent, CpuType cpuType, 18 | const QString &data, quint64 offset) 19 | : QDialog{parent}, cpuType{cpuType}, offset{offset} 20 | { 21 | setWindowTitle(tr("Disassembler")); 22 | createLayout(); 23 | resize(400, 300); 24 | Util::centerWidget(this); 25 | 26 | if (!data.isEmpty()) { 27 | machineText->setText(data); 28 | convertBtn->click(); 29 | } 30 | } 31 | 32 | void DisassemblerDialog::onConvert() { 33 | QString text = machineText->toPlainText(); 34 | if (text.isEmpty()) { 35 | setAsmVisible(false); 36 | machineText->setFocus(); 37 | QMessageBox::warning(this, "bmod", tr("Write some machine code!")); 38 | return; 39 | } 40 | 41 | quint64 offset = offsetEdit->text().toULongLong(nullptr, 16); 42 | 43 | auto obj = BinaryObjectPtr(new BinaryObject(cpuType)); 44 | Disassembler dis(obj); 45 | Disassembly result; 46 | if (dis.disassemble(text, result, offset)) { 47 | asmText->setText(result.asmLines.join("\n")); 48 | setAsmVisible(); 49 | } 50 | else { 51 | setAsmVisible(false); 52 | machineText->setFocus(); 53 | QMessageBox::warning(this, "bmod", 54 | tr("Could not disassemble machine code!")); 55 | } 56 | } 57 | 58 | void DisassemblerDialog::onCpuTypeIndexChanged(int index) { 59 | cpuType = (CpuType) cpuTypeBox->itemData(index).toInt(); 60 | } 61 | 62 | void DisassemblerDialog::createLayout() { 63 | machineText = new QTextEdit; 64 | machineText->setTabChangesFocus(true); 65 | 66 | offsetEdit = new QLineEdit; 67 | offsetEdit->setText(QString::number(offset, 16)); 68 | offsetEdit->setAlignment(Qt::AlignRight); 69 | offsetEdit->setFixedWidth(140); 70 | offsetEdit->setValidator(new QRegExpValidator(QRegExp("[A-Fa-f0-9]{1,16}"), this)); 71 | 72 | auto *offsetLayout = new QHBoxLayout; 73 | offsetLayout->setContentsMargins(0, 0, 0, 0); 74 | offsetLayout->addWidget(new QLabel(tr("Hex offset:"))); 75 | offsetLayout->addWidget(offsetEdit); 76 | offsetLayout->addStretch(); 77 | 78 | auto *machineLayout = new QVBoxLayout; 79 | machineLayout->setContentsMargins(0, 0, 0, 0); 80 | machineLayout->addWidget(new QLabel(tr("Machine code:"))); 81 | machineLayout->addWidget(machineText); 82 | machineLayout->addLayout(offsetLayout); 83 | 84 | auto *machineWidget = new QWidget; 85 | machineWidget->setLayout(machineLayout); 86 | 87 | asmText = new QTextEdit; 88 | asmText->setReadOnly(true); 89 | asmText->setTabChangesFocus(true); 90 | 91 | auto *asmLayout = new QVBoxLayout; 92 | asmLayout->setContentsMargins(0, 0, 0, 0); 93 | asmLayout->addWidget(new QLabel(tr("Disassembly:"))); 94 | asmLayout->addWidget(asmText); 95 | 96 | auto *asmWidget = new QWidget; 97 | asmWidget->setLayout(asmLayout); 98 | 99 | splitter = new QSplitter(Qt::Vertical); 100 | splitter->addWidget(machineWidget); 101 | splitter->addWidget(asmWidget); 102 | 103 | splitter->setCollapsible(0, false); 104 | splitter->setCollapsible(1, true); 105 | setAsmVisible(false); 106 | 107 | cpuTypeBox = new QComboBox; 108 | cpuTypeBox->addItem(tr("X86"), (int) CpuType::X86); 109 | cpuTypeBox->addItem(tr("X86_64"), (int) CpuType::X86_64); 110 | 111 | int idx = cpuTypeBox->findData((int) cpuType); 112 | if (idx != -1) { 113 | cpuTypeBox->setCurrentIndex(idx); 114 | } 115 | 116 | connect(cpuTypeBox, SIGNAL(currentIndexChanged(int)), 117 | this, SLOT(onCpuTypeIndexChanged(int))); 118 | 119 | convertBtn = new QPushButton(tr("Disassemble")); 120 | connect(convertBtn, &QPushButton::clicked, 121 | this, &DisassemblerDialog::onConvert); 122 | 123 | auto *bottomLayout = new QHBoxLayout; 124 | bottomLayout->addWidget(new QLabel(tr("CPU:"))); 125 | bottomLayout->addWidget(cpuTypeBox); 126 | bottomLayout->addStretch(); 127 | bottomLayout->addWidget(convertBtn); 128 | 129 | auto *layout = new QVBoxLayout; 130 | layout->setContentsMargins(5, 5, 5, 5); 131 | layout->addWidget(splitter); 132 | layout->addLayout(bottomLayout); 133 | 134 | setLayout(layout); 135 | } 136 | 137 | void DisassemblerDialog::setAsmVisible(bool visible) { 138 | splitter->setSizes(QList{1, visible ? 1 : 0}); 139 | if (!visible) asmText->clear(); 140 | } 141 | -------------------------------------------------------------------------------- /src/widgets/DisassemblerDialog.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_DISASSEMBLER_DIALOG_H 2 | #define BMOD_DISASSEMBLER_DIALOG_H 3 | 4 | #include 5 | 6 | #include "../CpuType.h" 7 | 8 | class QTextEdit; 9 | class QSplitter; 10 | class QLineEdit; 11 | class QComboBox; 12 | class QPushButton; 13 | 14 | class DisassemblerDialog : public QDialog { 15 | Q_OBJECT 16 | 17 | public: 18 | DisassemblerDialog(QWidget *parent = nullptr, 19 | CpuType cpuType = CpuType::X86, 20 | const QString &data = QString(), 21 | quint64 offset = 0); 22 | 23 | private slots: 24 | void onConvert(); 25 | void onCpuTypeIndexChanged(int index); 26 | 27 | private: 28 | void createLayout(); 29 | void setAsmVisible(bool visible = true); 30 | 31 | CpuType cpuType; 32 | quint64 offset; 33 | 34 | QTextEdit *machineText, *asmText; 35 | QSplitter *splitter; 36 | QLineEdit *offsetEdit; 37 | QComboBox *cpuTypeBox; 38 | QPushButton *convertBtn; 39 | }; 40 | 41 | #endif // BMOD_DISASSEMBLER_DIALOG_H 42 | -------------------------------------------------------------------------------- /src/widgets/LineEdit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "LineEdit.h" 4 | 5 | LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent) { } 6 | 7 | void LineEdit::focusOutEvent(QFocusEvent *event) { 8 | QLineEdit::focusOutEvent(event); 9 | emit focusLost(); 10 | } 11 | 12 | void LineEdit::keyPressEvent(QKeyEvent *event) { 13 | if (event->key() == Qt::Key_Up) { 14 | emit keyUp(); 15 | } 16 | else if (event->key() == Qt::Key_Down) { 17 | emit keyDown(); 18 | } 19 | else { 20 | QLineEdit::keyPressEvent(event); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/widgets/LineEdit.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_LINE_EDIT_H 2 | #define BMOD_LINE_EDIT_H 3 | 4 | #include 5 | 6 | class LineEdit : public QLineEdit { 7 | Q_OBJECT 8 | 9 | public: 10 | LineEdit(QWidget *parent = nullptr); 11 | 12 | signals: 13 | void focusLost(); 14 | void keyUp(); 15 | void keyDown(); 16 | 17 | protected: 18 | void focusOutEvent(QFocusEvent *event); 19 | void keyPressEvent(QKeyEvent *event); 20 | }; 21 | 22 | #endif // BMOD_LINE_EDIT_H 23 | -------------------------------------------------------------------------------- /src/widgets/MachineCodeWidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../Util.h" 11 | #include "TreeWidget.h" 12 | #include "MachineCodeWidget.h" 13 | 14 | namespace { 15 | class ItemDelegate : public QStyledItemDelegate { 16 | public: 17 | ItemDelegate(MachineCodeWidget *widget, QTreeWidget *tree, SectionPtr sec) 18 | : widget{widget}, tree{tree}, sec{sec} 19 | { } 20 | 21 | QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, 22 | const QModelIndex &index) const { 23 | int col = index.column(); 24 | if (col != 1 && col != 2) { 25 | return nullptr; 26 | } 27 | 28 | QString text = index.data().toString().trimmed(); 29 | if (text.isEmpty()) { 30 | return nullptr; 31 | } 32 | 33 | QString mask; 34 | int blocks = text.split(" ").size(); 35 | for (int i = 0; i < blocks; i++) { 36 | mask += "HH "; 37 | } 38 | if (mask.endsWith(" ")) { 39 | mask.chop(1); 40 | } 41 | 42 | auto *edit = new QLineEdit(parent); 43 | edit->setInputMask(mask); 44 | edit->setText(text); 45 | return edit; 46 | } 47 | 48 | void setModelData(QWidget *editor, QAbstractItemModel *model, 49 | const QModelIndex &index) const { 50 | auto *edit = qobject_cast(editor); 51 | if (edit) { 52 | QString oldStr = model->data(index).toString().trimmed(); 53 | QString newStr = edit->text().toUpper(); 54 | if (newStr == oldStr) { 55 | return; 56 | } 57 | model->setData(index, newStr); 58 | auto *item = tree->topLevelItem(index.row()); 59 | if (item) { 60 | int col = index.column(); 61 | Util::setTreeItemMarked(item, col); 62 | 63 | // Generate new ASCII representation. 64 | QString oldAscii = item->text(3); 65 | QString newAscii = Util::hexToAscii(newStr, 0, 8); 66 | if (col == 1) { 67 | newAscii += oldAscii.mid(8); 68 | } 69 | else { 70 | newAscii = oldAscii.mid(0, 8) + newAscii; 71 | } 72 | item->setText(3, newAscii); 73 | 74 | // Change region. 75 | quint64 addr = item->text(0).toULongLong(nullptr, 16); 76 | quint64 pos = (addr - sec->getAddress()) + (col - 1) * 8; 77 | QByteArray data = Util::hexToData(newStr.replace(" ", "")); 78 | sec->setSubData(data, pos); 79 | 80 | emit widget->modified(); 81 | } 82 | } 83 | } 84 | 85 | private: 86 | MachineCodeWidget *widget; 87 | QTreeWidget *tree; 88 | SectionPtr sec; 89 | }; 90 | } 91 | 92 | MachineCodeWidget::MachineCodeWidget(BinaryObjectPtr obj, SectionPtr sec) 93 | : obj{obj}, sec{sec}, shown{false} 94 | { 95 | setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 96 | createLayout(); 97 | } 98 | 99 | void MachineCodeWidget::showEvent(QShowEvent *event) { 100 | QWidget::showEvent(event); 101 | if (!shown) { 102 | shown = true; 103 | setup(); 104 | } 105 | else if (sec->isModified()) { 106 | QDateTime mod = sec->modifiedWhen(); 107 | if (secModified.isNull() || mod != secModified) { 108 | secModified = mod; 109 | setup(); 110 | } 111 | } 112 | } 113 | 114 | void MachineCodeWidget::createLayout() { 115 | label = new QLabel; 116 | 117 | treeWidget = new TreeWidget; 118 | treeWidget->setHeaderLabels(QStringList{tr("Address"), tr("Data Low"), 119 | tr("Data High"), tr("ASCII")}); 120 | treeWidget->setColumnWidth(0, obj->getSystemBits() == 64 ? 110 : 70); 121 | treeWidget->setColumnWidth(1, 200); 122 | treeWidget->setColumnWidth(2, 200); 123 | treeWidget->setColumnWidth(3, 110); 124 | treeWidget->setItemDelegate(new ItemDelegate(this, treeWidget, sec)); 125 | treeWidget->setMachineCodeColumns(QList{1, 2}); 126 | treeWidget->setCpuType(obj->getCpuType()); 127 | treeWidget->setAddressColumn(0); 128 | 129 | auto *layout = new QVBoxLayout; 130 | layout->setContentsMargins(0, 0, 0, 0); 131 | layout->addWidget(label); 132 | layout->addWidget(treeWidget); 133 | 134 | setLayout(layout); 135 | } 136 | 137 | void MachineCodeWidget::setup() { 138 | treeWidget->clear(); 139 | 140 | quint64 addr = sec->getAddress(); 141 | const QByteArray &data = sec->getData(); 142 | int len = data.size(), rows = len / 16; 143 | 144 | if (len == 0) { 145 | label->setText(tr("Defined but empty.")); 146 | treeWidget->hide(); 147 | return; 148 | } 149 | 150 | if (len % 16 > 0) rows++; 151 | 152 | QProgressDialog progDiag(this); 153 | progDiag.setLabelText(tr("Processing data..")); 154 | progDiag.setCancelButton(nullptr); 155 | progDiag.setRange(0, 100); 156 | progDiag.show(); 157 | qApp->processEvents(); 158 | 159 | for (int row = 0, byte = 0; row < rows; row++) { 160 | auto *item = new QTreeWidgetItem; 161 | item->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); 162 | item->setText(0, Util::padString(QString::number(addr, 16).toUpper(), 163 | obj->getSystemBits() / 8)); 164 | 165 | QString code, ascii; 166 | for (int cur = 0; cur < 16 && byte < len; cur++, byte++) { 167 | QString hex = 168 | Util::padString(QString::number((unsigned char) data[byte], 16), 2); 169 | code += hex + " "; 170 | 171 | ascii += Util::dataToAscii(data, byte, 1); 172 | } 173 | if (code.endsWith(" ")) { 174 | code.chop(1); 175 | } 176 | code = code.toUpper(); 177 | item->setText(1, code.mid(0, 8 * 3)); 178 | item->setText(2, code.mid(8 * 3)); 179 | item->setText(3, ascii); 180 | 181 | treeWidget->addTopLevelItem(item); 182 | addr += 16; 183 | 184 | static int lastPerc{0}; 185 | int perc = (float) row / (float) rows * 100.0; 186 | if (perc > lastPerc || perc == 100) { 187 | lastPerc = perc; 188 | progDiag.setValue(perc); 189 | progDiag.setLabelText(tr("Processing data.. %1% (%2 of %3)") 190 | .arg(perc) 191 | .arg(Util::formatSize(byte)) 192 | .arg(Util::formatSize(len))); 193 | qApp->processEvents(); 194 | } 195 | } 196 | 197 | // Mark items as modified if a region states it. 198 | const auto &modRegs = sec->getModifiedRegions(); 199 | for (int row = 0, byte = 0; row < rows; row++, byte += 16) { 200 | auto *item = treeWidget->topLevelItem(row); 201 | foreach (const auto ®, modRegs) { 202 | if (reg.first >= byte && reg.first < byte + 16) { 203 | int col1 = 1, col2 = 2; 204 | if (reg.first < byte + 8) { 205 | Util::setTreeItemMarked(item, col1); 206 | } 207 | if (reg.first + reg.second >= byte + 8) { 208 | Util::setTreeItemMarked(item, col2); 209 | } 210 | if (reg.first + reg.second > byte + 16) { 211 | // Number of additional rows to mark. 212 | int num = ((reg.first + reg.second) - (byte + 16)) / 16; 213 | for (int j = 0; j < num + 1; j++) { 214 | item = treeWidget->topLevelItem(row + j + 1); 215 | if (item) { 216 | Util::setTreeItemMarked(item, col1); 217 | 218 | // If intermediate rows or if the data actually spans 219 | // the last column. 220 | if (j < num || (reg.first + reg.second) % 16 > 8) { 221 | Util::setTreeItemMarked(item, col2); 222 | } 223 | } 224 | } 225 | } 226 | } 227 | } 228 | } 229 | 230 | int padSize = obj->getSystemBits() / 8; 231 | addr = sec->getAddress(); 232 | label->setText(tr("Section size: %1, address %2 to %3, %4 rows") 233 | .arg(Util::formatSize(len)) 234 | .arg(Util::padString(QString::number(addr, 16).toUpper(), 235 | padSize)) 236 | .arg(Util::padString(QString::number(addr + len, 16).toUpper(), 237 | padSize)) 238 | .arg(treeWidget->topLevelItemCount())); 239 | 240 | treeWidget->setFocus(); 241 | } 242 | -------------------------------------------------------------------------------- /src/widgets/MachineCodeWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_MACHINE_CODE_WIDGET_H 2 | #define BMOD_MACHINE_CODE_WIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | #include "../Section.h" 8 | #include "../BinaryObject.h" 9 | 10 | class QLabel; 11 | class TreeWidget; 12 | class QTreeWidgetItem; 13 | 14 | class MachineCodeWidget : public QWidget { 15 | Q_OBJECT 16 | 17 | public: 18 | MachineCodeWidget(BinaryObjectPtr obj, SectionPtr sec); 19 | 20 | signals: 21 | void modified(); 22 | 23 | protected: 24 | void showEvent(QShowEvent *event); 25 | 26 | private: 27 | void createLayout(); 28 | void setup(); 29 | void setItemMarked(QTreeWidgetItem *item, int column); 30 | 31 | BinaryObjectPtr obj; 32 | SectionPtr sec; 33 | QDateTime secModified; 34 | 35 | bool shown; 36 | QLabel *label; 37 | TreeWidget *treeWidget; 38 | }; 39 | 40 | #endif // BMOD_MACHINE_CODE_WIDGET_H 41 | -------------------------------------------------------------------------------- /src/widgets/MainWindow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../Util.h" 13 | #include "MainWindow.h" 14 | #include "BinaryWidget.h" 15 | #include "ConversionHelper.h" 16 | #include "../formats/Format.h" 17 | #include "PreferencesDialog.h" 18 | #include "DisassemblerDialog.h" 19 | 20 | MainWindow::MainWindow(const QStringList &files) 21 | : shown{false}, modified{false}, startupFiles{files} 22 | { 23 | // Remove possible duplicates. 24 | startupFiles = startupFiles.toSet().toList(); 25 | 26 | setWindowTitle("bmod"); 27 | readSettings(); 28 | createLayout(); 29 | createMenu(); 30 | } 31 | 32 | MainWindow::~MainWindow() { 33 | writeSettings(); 34 | } 35 | 36 | void MainWindow::showEvent(QShowEvent *event) { 37 | QMainWindow::showEvent(event); 38 | 39 | if (shown) return; 40 | shown = true; 41 | 42 | if (geometry.isEmpty()) { 43 | resize(900, 500); 44 | Util::centerWidget(this); 45 | } 46 | else { 47 | restoreGeometry(geometry); 48 | } 49 | 50 | // Load specified files or open file dialog. 51 | if (startupFiles.isEmpty()) { 52 | openBinary(); 53 | } 54 | else { 55 | foreach (const auto &file, startupFiles) { 56 | loadBinary(file); 57 | } 58 | } 59 | } 60 | 61 | void MainWindow::closeEvent(QCloseEvent *event) { 62 | if (config.getConfirmQuit() && modified) { 63 | auto answer = 64 | QMessageBox::question(this, "bmod", 65 | tr("Are you sure you want to quit without saving changes?")); 66 | if (answer == QMessageBox::No) { 67 | event->ignore(); 68 | return; 69 | } 70 | } 71 | 72 | event->accept(); 73 | } 74 | 75 | void MainWindow::openBinary() { 76 | QFileDialog diag(this, tr("Open Binary"), QDir::homePath()); 77 | diag.setNameFilters(QStringList{"Mach-O binary (*.o *.dylib *.bundle *)", 78 | "Any file (*)"}); 79 | if (!diag.exec()) { 80 | if (binaryWidgets.isEmpty()) { 81 | qApp->quit(); 82 | } 83 | return; 84 | } 85 | 86 | QString file = diag.selectedFiles().first(); 87 | foreach (const auto *binary, binaryWidgets) { 88 | if (binary->getFile() == file) { 89 | QMessageBox::warning(this, "bmod", tr("Can't open same binary twice!")); 90 | return; 91 | } 92 | } 93 | 94 | loadBinary(file); 95 | } 96 | 97 | void MainWindow::saveBinary() { 98 | if (binaryWidgets.isEmpty()) { 99 | return; 100 | } 101 | 102 | int idx = tabWidget->currentIndex(); 103 | auto *binary = binaryWidgets[idx]; 104 | 105 | if (config.getConfirmCommit()) { 106 | auto answer = 107 | QMessageBox::question(this, "bmod", 108 | tr("Are you sure you want to commit changes to file \"%1\"?") 109 | .arg(binary->getFile())); 110 | if (answer == QMessageBox::No) { 111 | return; 112 | } 113 | } 114 | 115 | if (config.getBackupEnabled()) { 116 | bool backup{true}; 117 | if (config.getBackupAsk()) { 118 | auto answer = 119 | QMessageBox::question(this, "bmod", 120 | tr("Do you want to save a backup before committing?" 121 | "\n(This dialog can be turned off in Preferences.)")); 122 | backup = (answer == QMessageBox::Yes); 123 | } 124 | if (backup) { 125 | saveBackup(binary->getFile()); 126 | } 127 | } 128 | 129 | binary->commit(); 130 | 131 | QString text = tabWidget->tabText(idx); 132 | if (text.endsWith(" *")) { 133 | text.chop(2); 134 | tabWidget->setTabText(idx, text); 135 | } 136 | } 137 | 138 | void MainWindow::closeBinary() { 139 | int idx = tabWidget->currentIndex(); 140 | if (idx != -1) { 141 | auto answer = 142 | QMessageBox::question(this, "bmod", 143 | tr("Are you sure you want to close the binary?")); 144 | if (answer == QMessageBox::No) { 145 | return; 146 | } 147 | 148 | tabWidget->removeTab(idx); 149 | delete binaryWidgets.takeAt(idx); 150 | } 151 | 152 | if (binaryWidgets.isEmpty()) { 153 | qApp->quit(); 154 | } 155 | } 156 | 157 | void MainWindow::showPreferences() { 158 | PreferencesDialog diag(config); 159 | diag.exec(); 160 | config.save(); 161 | } 162 | 163 | void MainWindow::showConversionHelper() { 164 | auto *helper = new ConversionHelper(this); 165 | helper->show(); 166 | } 167 | 168 | void MainWindow::showDisassembler() { 169 | auto *disass = new DisassemblerDialog(this); 170 | disass->show(); 171 | } 172 | 173 | void MainWindow::onRecentFile() { 174 | auto *action = qobject_cast(sender()); 175 | if (!action) return; 176 | loadBinary(action->text()); 177 | } 178 | 179 | void MainWindow::onBinaryObjectModified() { 180 | auto *bin = qobject_cast(sender()); 181 | if (!bin) return; 182 | 183 | modified = true; 184 | int idx = tabWidget->currentIndex(); 185 | QString text = tabWidget->tabText(idx); 186 | if (!text.endsWith(" *")) { 187 | tabWidget->setTabText(idx, text + " *"); 188 | } 189 | } 190 | 191 | void MainWindow::readSettings() { 192 | QSettings settings; 193 | geometry = settings.value("MainWindow_geometry", QByteArray()).toByteArray(); 194 | 195 | recentFiles = 196 | settings.value("MainWindow_recent_files", QStringList()).toStringList(); 197 | for (int i = recentFiles.size() - 1; i >= 0; i--) { 198 | if (!QFile::exists(recentFiles[i])) { 199 | recentFiles.removeAt(i); 200 | } 201 | } 202 | if (recentFiles.size() > 10) { 203 | recentFiles = recentFiles.mid(recentFiles.size() - 10); 204 | } 205 | } 206 | 207 | void MainWindow::writeSettings() { 208 | QSettings settings; 209 | settings.setValue("MainWindow_geometry", saveGeometry()); 210 | settings.setValue("MainWindow_recent_files", recentFiles); 211 | } 212 | 213 | void MainWindow::createLayout() { 214 | tabWidget = new QTabWidget; 215 | 216 | auto *layout = new QVBoxLayout; 217 | layout->addWidget(tabWidget); 218 | 219 | auto *w = new QWidget; 220 | w->setLayout(layout); 221 | setCentralWidget(w); 222 | } 223 | 224 | void MainWindow::createMenu() { 225 | QMenu *fileMenu = menuBar()->addMenu(tr("File")); 226 | fileMenu->addAction(tr("Open binary"), this, SLOT(openBinary()), 227 | QKeySequence::Open); 228 | 229 | if (!recentFiles.isEmpty()) { 230 | QMenu *recentMenu = fileMenu->addMenu(tr("Open recent files")); 231 | foreach (const auto &file, recentFiles) { 232 | recentMenu->addAction(file, this, SLOT(onRecentFile())); 233 | } 234 | } 235 | 236 | fileMenu->addAction(tr("Save binary"), this, SLOT(saveBinary()), 237 | QKeySequence::Save); 238 | fileMenu->addAction(tr("Close binary"), this, SLOT(closeBinary()), 239 | QKeySequence::Close); 240 | #ifndef MAC 241 | fileMenu->addSeparator(); 242 | #endif 243 | fileMenu->addAction(tr("Preferences"), this, SLOT(showPreferences()), 244 | QKeySequence(Qt::CTRL + Qt::Key_P)); 245 | 246 | QMenu *toolsMenu = menuBar()->addMenu(tr("Tools")); 247 | toolsMenu->addAction(tr("Conversion helper"), 248 | this, SLOT(showConversionHelper()), 249 | QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_C)); 250 | toolsMenu->addAction(tr("Disassembler"), 251 | this, SLOT(showDisassembler()), 252 | QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_D)); 253 | } 254 | 255 | void MainWindow::loadBinary(QString file) { 256 | // If .app then resolve the internal binary file. 257 | QString appBin = Util::resolveAppBinary(file); 258 | if (!appBin.isEmpty()) { 259 | file = appBin; 260 | } 261 | 262 | QProgressDialog progDiag(this); 263 | progDiag.setLabelText(tr("Detecting format..")); 264 | progDiag.setCancelButton(nullptr); 265 | progDiag.setRange(0, 0); 266 | progDiag.show(); 267 | qApp->processEvents(); 268 | 269 | auto fmt = Format::detect(file); 270 | if (fmt == nullptr) { 271 | QMessageBox::critical(this, "bmod", tr("Unknown file - could not open!")); 272 | return; 273 | } 274 | 275 | qDebug() << "detected:" << Util::formatTypeString(fmt->getType()); 276 | 277 | progDiag.setLabelText(tr("Reading and parsing binary..")); 278 | qApp->processEvents(); 279 | if (!fmt->parse()) { 280 | QMessageBox::warning(this, "bmod", tr("Could not parse file!")); 281 | return; 282 | } 283 | 284 | // Add recent file. 285 | if (!recentFiles.contains(file)) { 286 | recentFiles << file; 287 | } 288 | if (recentFiles.size() > 10) { 289 | recentFiles.removeFirst(); 290 | } 291 | 292 | auto *binWidget = new BinaryWidget(fmt); 293 | connect(binWidget, &BinaryWidget::modified, 294 | this, &MainWindow::onBinaryObjectModified); 295 | binaryWidgets << binWidget; 296 | int idx = tabWidget->addTab(binWidget, QFileInfo(file).fileName()); 297 | tabWidget->setCurrentIndex(idx); 298 | } 299 | 300 | void MainWindow::saveBackup(const QString &file) { 301 | // Determine if prior backups have been made and, if so, how many. 302 | QFileInfo fi(file); 303 | QDir dir = fi.dir(); 304 | int bakCount = 0, bakNum = 0; 305 | QStringList files; 306 | foreach (const auto &entry, 307 | dir.entryInfoList(QStringList{QString("%1.bak*").arg(fi.fileName())}, 308 | QDir::Files, QDir::Name)) { 309 | files << entry.absoluteFilePath(); 310 | bakCount++; 311 | QString ext = entry.suffix(); 312 | static QRegExp re("bak([\\d]+)$"); 313 | if (re.indexIn(ext) != -1 && re.captureCount() == 1) { 314 | bakNum = re.capturedTexts()[1].toUInt(); 315 | } 316 | } 317 | 318 | // Remove previous backups if not unlimited. And remove one due to 319 | // the file that will be created beneath. 320 | int maxAmount = config.getBackupAmount(); 321 | if (maxAmount > 0 && bakCount >= maxAmount - 1) { 322 | for (int i = 0; i < bakCount - (maxAmount - 1); i++) { 323 | QFile::remove(files[i]); 324 | } 325 | } 326 | 327 | QString num = Util::padString(QString::number(++bakNum), 4), 328 | dest = QString("%1.bak%2").arg(file).arg(num); 329 | if (!QFile::copy(file, dest)) { 330 | QMessageBox::warning(this, "bmod", 331 | tr("Could not save backup to \"%1\"!").arg(dest)); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/widgets/MainWindow.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_MAIN_WINDOW_H 2 | #define BMOD_MAIN_WINDOW_H 3 | 4 | #include 5 | #include 6 | 7 | #include "Config.h" 8 | 9 | class QTabWidget; 10 | class QStringList; 11 | class BinaryWidget; 12 | 13 | class MainWindow : public QMainWindow { 14 | Q_OBJECT 15 | 16 | public: 17 | MainWindow(const QStringList &files = QStringList()); 18 | ~MainWindow(); 19 | 20 | protected: 21 | void showEvent(QShowEvent *event); 22 | void closeEvent(QCloseEvent *event); 23 | 24 | private slots: 25 | void openBinary(); 26 | void saveBinary(); 27 | void closeBinary(); 28 | void showPreferences(); 29 | void showConversionHelper(); 30 | void showDisassembler(); 31 | void onRecentFile(); 32 | void onBinaryObjectModified(); 33 | 34 | private: 35 | void readSettings(); 36 | void writeSettings(); 37 | void createLayout(); 38 | void createMenu(); 39 | 40 | void loadBinary(QString file); 41 | void saveBackup(const QString &file); 42 | 43 | Config config; 44 | bool shown, modified; 45 | QStringList recentFiles, startupFiles; 46 | QByteArray geometry; 47 | 48 | QTabWidget *tabWidget; 49 | QList binaryWidgets; 50 | }; 51 | 52 | #endif // BMOD_MAIN_WINDOW_H 53 | -------------------------------------------------------------------------------- /src/widgets/PreferencesDialog.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../Config.h" 10 | #include "PreferencesDialog.h" 11 | 12 | PreferencesDialog::PreferencesDialog(Config &config) : config{config} { 13 | setWindowTitle(tr("Preferences")); 14 | createLayout(); 15 | createTabs(); 16 | } 17 | 18 | void PreferencesDialog::onConfirmCommitChanged(int state) { 19 | config.setConfirmCommit(state == Qt::Checked); 20 | } 21 | 22 | void PreferencesDialog::onConfirmQuitChanged(int state) { 23 | config.setConfirmQuit(state == Qt::Checked); 24 | } 25 | 26 | void PreferencesDialog::onBackupsToggled(bool on) { 27 | config.setBackupEnabled(on); 28 | } 29 | 30 | void PreferencesDialog::onBackupAskChanged(int state) { 31 | config.setBackupAsk(state == Qt::Checked); 32 | } 33 | 34 | void PreferencesDialog::onBackupAmountChanged(int amount) { 35 | config.setBackupAmount(amount); 36 | backupAmountInfo->setVisible(amount == 0); 37 | } 38 | 39 | void PreferencesDialog::createLayout() { 40 | tabWidget = new QTabWidget; 41 | 42 | auto *layout = new QVBoxLayout; 43 | layout->setContentsMargins(5, 5, 5, 5); 44 | layout->addWidget(tabWidget); 45 | 46 | setLayout(layout); 47 | } 48 | 49 | void PreferencesDialog::createTabs() { 50 | auto *generalConfirmCommitChk = 51 | new QCheckBox(tr("Confirm before committing to a binary.")); 52 | generalConfirmCommitChk->setChecked(config.getConfirmCommit()); 53 | connect(generalConfirmCommitChk, &QCheckBox::stateChanged, 54 | this, &PreferencesDialog::onConfirmCommitChanged); 55 | 56 | auto *generalConfirmQuitChk = 57 | new QCheckBox(tr("Confirm before quitting if any binaries are modified.")); 58 | generalConfirmQuitChk->setChecked(config.getConfirmQuit()); 59 | connect(generalConfirmQuitChk, &QCheckBox::stateChanged, 60 | this, &PreferencesDialog::onConfirmQuitChanged); 61 | 62 | auto *generalLayout = new QVBoxLayout; 63 | generalLayout->addWidget(generalConfirmCommitChk); 64 | generalLayout->addWidget(generalConfirmQuitChk); 65 | generalLayout->addStretch(); 66 | 67 | auto *generalWidget = new QWidget; 68 | generalWidget->setLayout(generalLayout); 69 | 70 | auto *backupWidget = 71 | new QGroupBox(tr("Enable backups when committing changes to binaries.")); 72 | backupWidget->setCheckable(true); 73 | backupWidget->setChecked(config.getBackupEnabled()); 74 | connect(backupWidget, &QGroupBox::toggled, 75 | this, &PreferencesDialog::onBackupsToggled); 76 | 77 | auto *backupLbl = 78 | new QLabel(tr("Backups are saved in the same folder as the originating " 79 | "binary file but with a post-fix of the form \".bakN\", where " 80 | "\"N\" is the backup number.")); 81 | backupLbl->setWordWrap(true); 82 | 83 | auto *backupAmountLbl = new QLabel(tr("Number of copies to keep:")); 84 | 85 | auto *backupAmountSpin = new QSpinBox; 86 | backupAmountSpin->setRange(0, 1024); 87 | backupAmountSpin->setValue(config.getBackupAmount()); 88 | connect(backupAmountSpin, SIGNAL(valueChanged(int)), 89 | this, SLOT(onBackupAmountChanged(int))); 90 | 91 | backupAmountInfo = new QLabel(tr("(Unlimited)")); 92 | backupAmountInfo->setVisible(config.getBackupAmount() == 0); 93 | 94 | auto *backupAmountLayout = new QHBoxLayout; 95 | backupAmountLayout->addWidget(backupAmountLbl); 96 | backupAmountLayout->addWidget(backupAmountSpin); 97 | backupAmountLayout->addWidget(backupAmountInfo); 98 | backupAmountLayout->addStretch(); 99 | 100 | auto *backupAskChk = 101 | new QCheckBox(tr("Ask before each commit whether to save backup or not.")); 102 | backupAskChk->setChecked(config.getBackupAsk()); 103 | connect(backupAskChk, &QCheckBox::stateChanged, 104 | this, &PreferencesDialog::onBackupAskChanged); 105 | 106 | /* 107 | auto *backupCustomChk = 108 | new QCheckBox(tr("Set custom output folder.")); 109 | */ 110 | 111 | auto *backupLayout = new QVBoxLayout; 112 | backupLayout->addWidget(backupLbl); 113 | backupLayout->addLayout(backupAmountLayout); 114 | backupLayout->addWidget(backupAskChk); 115 | //backupLayout->addWidget(backupCustomChk); 116 | backupLayout->addStretch(); 117 | backupWidget->setLayout(backupLayout); 118 | 119 | tabWidget->addTab(generalWidget, tr("General")); 120 | tabWidget->addTab(backupWidget, tr("Backup")); 121 | } 122 | -------------------------------------------------------------------------------- /src/widgets/PreferencesDialog.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_PREFERENCES_DIALOG_H 2 | #define BMOD_PREFERENCES_DIALOG_H 3 | 4 | #include 5 | 6 | class Config; 7 | class QLabel; 8 | class QTabWidget; 9 | 10 | class PreferencesDialog : public QDialog { 11 | Q_OBJECT 12 | 13 | public: 14 | PreferencesDialog(Config &config); 15 | 16 | private slots: 17 | void onConfirmCommitChanged(int state); 18 | void onConfirmQuitChanged(int state); 19 | void onBackupsToggled(bool on); 20 | void onBackupAskChanged(int state); 21 | void onBackupAmountChanged(int amount); 22 | 23 | private: 24 | void createLayout(); 25 | void createTabs(); 26 | 27 | Config &config; 28 | 29 | QTabWidget *tabWidget; 30 | QLabel *backupAmountInfo; 31 | }; 32 | 33 | #endif // BMOD_PREFERENCES_DIALOG_H 34 | -------------------------------------------------------------------------------- /src/widgets/TreeWidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "LineEdit.h" 11 | #include "TreeWidget.h" 12 | #include "DisassemblerDialog.h" 13 | 14 | TreeWidget::TreeWidget(QWidget *parent) 15 | : QTreeWidget(parent), cpuType{CpuType::X86}, ctxItem{nullptr}, ctxCol{-1}, 16 | addrColumn{-1}, curCol{0}, curItem{0}, cur{0}, total{0} 17 | { 18 | setSelectionBehavior(QAbstractItemView::SelectItems); 19 | setSelectionMode(QAbstractItemView::SingleSelection); 20 | setEditTriggers(QAbstractItemView::DoubleClicked); 21 | setContextMenuPolicy(Qt::CustomContextMenu); 22 | connect(this, &QTreeWidget::customContextMenuRequested, 23 | this, &TreeWidget::onShowContextMenu); 24 | 25 | // Set fixed-width font. 26 | setFont(QFont("Courier")); 27 | 28 | searchEdit = new LineEdit(this); 29 | searchEdit->setVisible(false); 30 | searchEdit->setFixedWidth(150); 31 | searchEdit->setFixedHeight(21); 32 | searchEdit->setPlaceholderText(tr("Search query")); 33 | connect(searchEdit, &LineEdit::focusLost, 34 | this, &TreeWidget::onSearchLostFocus); 35 | connect(searchEdit, &LineEdit::keyDown, this, &TreeWidget::nextSearchResult); 36 | connect(searchEdit, &LineEdit::keyUp, this, &TreeWidget::prevSearchResult); 37 | connect(searchEdit, &LineEdit::returnPressed, 38 | this, &TreeWidget::onSearchReturnPressed); 39 | connect(searchEdit, &LineEdit::textEdited, this, &TreeWidget::onSearchEdited); 40 | 41 | searchLabel = new QLabel(this); 42 | searchLabel->setVisible(false); 43 | searchLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); 44 | searchLabel->setFixedHeight(searchEdit->height()); 45 | searchLabel->setStyleSheet("QLabel { " 46 | "background-color: #EEEEEE; " 47 | "border-top: 1px solid #CCCCCC; " 48 | "}"); 49 | } 50 | 51 | void TreeWidget::setMachineCodeColumns(const QList columns) { 52 | if (columns.isEmpty()) { 53 | machineCodeColumns.clear(); 54 | return; 55 | } 56 | 57 | int cols = columnCount(); 58 | foreach (int col, columns) { 59 | if (col < cols) { 60 | machineCodeColumns << col; 61 | } 62 | } 63 | machineCodeColumns = machineCodeColumns.toSet().toList(); 64 | if (machineCodeColumns.size() > cols) { 65 | machineCodeColumns.clear(); 66 | } 67 | } 68 | 69 | void TreeWidget::setAddressColumn(int column) { 70 | if (column < 0 || column > columnCount() - 1) { 71 | addrColumn = -1; 72 | return; 73 | } 74 | 75 | addrColumn = column; 76 | } 77 | 78 | void TreeWidget::keyPressEvent(QKeyEvent *event) { 79 | QTreeWidget::keyPressEvent(event); 80 | 81 | bool ctrl{false}; 82 | #ifdef MAC 83 | ctrl = event->modifiers() | Qt::MetaModifier; 84 | #else 85 | ctrl = event->modifiers() | Qt::ControlModifier; 86 | #endif 87 | if (ctrl && event->key() == Qt::Key_F) { 88 | doSearch(); 89 | } 90 | else if (event->key() == Qt::Key_Escape) { 91 | endSearch(); 92 | } 93 | } 94 | 95 | void TreeWidget::resizeEvent(QResizeEvent *event) { 96 | QTreeWidget::resizeEvent(event); 97 | 98 | if (searchEdit->isVisible()) { 99 | searchEdit->move(width() - searchEdit->width() - 1, 100 | height() - searchEdit->height() - 1); 101 | searchLabel->setFixedWidth(width() - searchEdit->width()); 102 | searchLabel->move(1, searchEdit->pos().y()); 103 | } 104 | } 105 | 106 | void TreeWidget::endSearch() { 107 | searchEdit->hide(); 108 | searchLabel->hide(); 109 | searchEdit->clear(); 110 | searchLabel->clear(); 111 | setFocus(); 112 | } 113 | 114 | void TreeWidget::onShowContextMenu(const QPoint &pos) { 115 | QMenu menu; 116 | menu.addAction("Search", this, SLOT(doSearch())); 117 | 118 | if (addrColumn != -1) { 119 | menu.addAction("Find address", this, SLOT(findAddress())); 120 | } 121 | 122 | ctxItem = itemAt(pos); 123 | if (ctxItem) { 124 | ctxCol = indexAt(pos).column(); 125 | 126 | menu.addSeparator(); 127 | menu.addAction("Copy field", this, SLOT(copyField())); 128 | menu.addAction("Copy row", this, SLOT(copyRow())); 129 | 130 | if (machineCodeColumns.contains(ctxCol)) { 131 | menu.addSeparator(); 132 | menu.addAction("Disassemble", this, SLOT(disassemble())); 133 | } 134 | } 135 | 136 | // Use cursor because mapToGlobal(pos) is off by the height of the 137 | // tree widget header anyway. 138 | menu.exec(QCursor::pos()); 139 | 140 | ctxItem = nullptr; 141 | ctxCol = -1; 142 | } 143 | 144 | void TreeWidget::doSearch() { 145 | searchEdit->move(width() - searchEdit->width() - 1, 146 | height() - searchEdit->height() - 1); 147 | searchEdit->show(); 148 | searchEdit->setFocus(); 149 | } 150 | 151 | void TreeWidget::disassemble() { 152 | if (!ctxItem) return; 153 | QString text = ctxItem->text(ctxCol); 154 | quint64 offset{0}; 155 | if (addrColumn != -1) { 156 | bool ok; 157 | offset = ctxItem->text(addrColumn).toULongLong(&ok, 16); 158 | if (!ok) offset = 0; 159 | } 160 | DisassemblerDialog diag(this, cpuType, text, offset); 161 | diag.exec(); 162 | } 163 | 164 | void TreeWidget::copyField() { 165 | if (!ctxItem) return; 166 | QString text = ctxItem->text(ctxCol); 167 | QApplication::clipboard()->setText(text); 168 | } 169 | 170 | void TreeWidget::copyRow() { 171 | if (!ctxItem) return; 172 | QString text; 173 | for (int i = 0; i < columnCount(); i++) { 174 | text += ctxItem->text(i); 175 | if (i < columnCount() - 1) { 176 | text += "\t"; 177 | } 178 | } 179 | QApplication::clipboard()->setText(text); 180 | } 181 | 182 | void TreeWidget::findAddress() { 183 | bool ok; 184 | QString text = 185 | QInputDialog::getText(this, tr("Find Address"), tr("Address (hex):"), 186 | QLineEdit::Normal, QString(), &ok); 187 | if (!ok || text.isEmpty()) { 188 | return; 189 | } 190 | 191 | quint64 num = text.toULongLong(&ok, 16); 192 | if (!ok) { 193 | QMessageBox::warning(this, "bmod", 194 | tr("Invalid address! Must be in hexadecimal.")); 195 | findAddress(); 196 | return; 197 | } 198 | 199 | int cnt = topLevelItemCount(); 200 | for (int i = 0; i < cnt; i++) { 201 | auto *item = topLevelItem(i); 202 | quint64 n = item->text(addrColumn).toULongLong(&ok, 16); 203 | if (!ok) continue; 204 | if (n == num) { 205 | setCurrentItem(item); 206 | scrollToItem(item, QAbstractItemView::PositionAtCenter); 207 | return; 208 | } 209 | 210 | if (i < cnt - 1) { 211 | auto *item2 = topLevelItem(i+1); 212 | quint64 n2 = item2->text(addrColumn).toULongLong(&ok, 16); 213 | if (!ok) continue; 214 | if (num >= n && num < n2) { 215 | setCurrentItem(item); 216 | scrollToItem(item, QAbstractItemView::PositionAtCenter); 217 | return; 218 | } 219 | } 220 | } 221 | 222 | QMessageBox::information(this, "bmdo", tr("Did not find anything.")); 223 | } 224 | 225 | void TreeWidget::resetSearch() { 226 | searchEdit->clear(); 227 | searchLabel->clear(); 228 | searchLabel->hide(); 229 | searchResults.clear(); 230 | lastQuery.clear(); 231 | curCol = curItem = cur = total = 0; 232 | } 233 | 234 | void TreeWidget::onSearchLostFocus() { 235 | if (searchEdit->isVisible() && searchEdit->text().isEmpty()) { 236 | endSearch(); 237 | } 238 | } 239 | 240 | void TreeWidget::onSearchReturnPressed() { 241 | QString query = searchEdit->text().trimmed(); 242 | if (query.isEmpty()) { 243 | resetSearch(); 244 | return; 245 | } 246 | 247 | if (query == lastQuery) { 248 | nextSearchResult(); 249 | return; 250 | } 251 | 252 | int cols = columnCount(); 253 | searchResults.clear(); 254 | total = 0; 255 | for (int col = 0; col < cols; col++) { 256 | auto res = findItems(query, Qt::MatchContains, col); 257 | if (!res.isEmpty()) { 258 | searchResults[col] = res; 259 | total += res.size(); 260 | } 261 | } 262 | 263 | if (searchResults.isEmpty()) { 264 | showSearchText(tr("No matches found")); 265 | return; 266 | } 267 | 268 | lastQuery = query; 269 | cur = 0; 270 | curCol = searchResults.keys().first(); 271 | curItem = 0; 272 | selectSearchResult(curCol, curItem); 273 | } 274 | 275 | void TreeWidget::selectSearchResult(int col, int item) { 276 | if (!searchResults.contains(col)) { 277 | return; 278 | } 279 | 280 | const auto &list = searchResults[col]; 281 | if (item < 0 || item > list.size() - 1) { 282 | return; 283 | } 284 | 285 | const auto &res = list[item]; 286 | 287 | showSearchText(tr("%1 of %2 matches").arg(cur + 1).arg(total)); 288 | 289 | // Select entry and not entire row. 290 | scrollToItem(res, QAbstractItemView::PositionAtCenter); 291 | int row = indexOfTopLevelItem(res); 292 | auto index = model()->index(row, col); 293 | selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent); 294 | } 295 | 296 | void TreeWidget::nextSearchResult() { 297 | const auto &list = searchResults[curCol]; 298 | int pos = curItem; 299 | pos++; 300 | if (pos > list.size() - 1) { 301 | curItem = 0; 302 | const auto &keys = searchResults.keys(); 303 | int pos2 = keys.indexOf(curCol); 304 | pos2++; 305 | if (pos2 > keys.size() - 1) { 306 | curCol = keys[0]; 307 | } 308 | else { 309 | curCol = keys[pos2]; 310 | } 311 | } 312 | else { 313 | curItem = pos; 314 | } 315 | 316 | cur++; 317 | if (cur > total - 1) { 318 | cur = 0; 319 | } 320 | 321 | selectSearchResult(curCol, curItem); 322 | } 323 | 324 | void TreeWidget::prevSearchResult() { 325 | int pos = curItem; 326 | pos--; 327 | if (pos < 0) { 328 | const auto &keys = searchResults.keys(); 329 | int pos2 = keys.indexOf(curCol); 330 | pos2--; 331 | if (pos2 < 0) { 332 | curCol = keys.last(); 333 | } 334 | else { 335 | curCol = keys[pos2]; 336 | } 337 | curItem = searchResults[curCol].size() - 1; 338 | } 339 | else { 340 | curItem = pos; 341 | } 342 | 343 | cur--; 344 | if (cur < 0) { 345 | cur = total - 1; 346 | } 347 | 348 | selectSearchResult(curCol, curItem); 349 | } 350 | 351 | void TreeWidget::onSearchEdited(const QString &text) { 352 | // If search was performed or no results were found then hide search 353 | // label when editing the field. 354 | if (!lastQuery.isEmpty() || searchResults.isEmpty()) { 355 | searchLabel->clear(); 356 | searchLabel->hide(); 357 | } 358 | } 359 | 360 | void TreeWidget::showSearchText(const QString &text) { 361 | searchLabel->setText(text + " "); 362 | searchLabel->setFixedWidth(width() - searchEdit->width()); 363 | searchLabel->move(1, searchEdit->pos().y()); 364 | searchLabel->show(); 365 | } 366 | -------------------------------------------------------------------------------- /src/widgets/TreeWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef BMOD_TREE_WIDGET_H 2 | #define BMOD_TREE_WIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../CpuType.h" 9 | 10 | class QLabel; 11 | class LineEdit; 12 | 13 | class TreeWidget : public QTreeWidget { 14 | Q_OBJECT 15 | 16 | public: 17 | TreeWidget(QWidget *parent = nullptr); 18 | 19 | void setCpuType(CpuType type) { cpuType = type; } 20 | void setMachineCodeColumns(const QList columns); 21 | 22 | void setAddressColumn(int column); 23 | 24 | protected: 25 | void keyPressEvent(QKeyEvent *event); 26 | void resizeEvent(QResizeEvent *event); 27 | 28 | private slots: 29 | void doSearch(); 30 | void endSearch(); 31 | void onSearchLostFocus(); 32 | void onSearchReturnPressed(); 33 | void nextSearchResult(); 34 | void prevSearchResult(); 35 | void onSearchEdited(const QString &text); 36 | void onShowContextMenu(const QPoint &pos); 37 | void disassemble(); 38 | void copyField(); 39 | void copyRow(); 40 | void findAddress(); 41 | 42 | private: 43 | void resetSearch(); 44 | void selectSearchResult(int col, int item); 45 | void showSearchText(const QString &text); 46 | 47 | QList machineCodeColumns; 48 | CpuType cpuType; 49 | QTreeWidgetItem *ctxItem; 50 | int ctxCol, addrColumn; 51 | 52 | QMap> searchResults; 53 | int curCol, curItem, cur, total; 54 | QString lastQuery; 55 | 56 | LineEdit *searchEdit; 57 | QLabel *searchLabel; 58 | }; 59 | 60 | #endif // BMOD_TREE_WIDGET_H 61 | --------------------------------------------------------------------------------