├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── ack.x86.gcc.O0.g.elf ├── context.cpp ├── context.h ├── decompiler.cpp ├── decompiler.h ├── demo.cpp ├── doc └── master.gif ├── function.cpp ├── function.h ├── place.cpp ├── place.h ├── token.cpp ├── token.h ├── ui.cpp ├── ui.h ├── yx.cpp └── yx.h /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /build/ 3 | /.cproject 4 | /.settings/ 5 | *.id0 6 | *.id1 7 | *.id2 8 | *.idb 9 | *.nam 10 | *.til -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | 3 | project(hexrays-demo CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_CXX_EXTENSIONS OFF) 8 | set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") 9 | 10 | # Check that obligatory parameters were defined. 11 | if(NOT IDA_SDK_DIR) 12 | message(FATAL_ERROR "Path to IDA SDK was not specified. " 13 | "Use -DIDA_SDK_DIR=.") 14 | endif() 15 | if(NOT EXISTS "${IDA_SDK_DIR}") 16 | message(FATAL_ERROR "Specified IDA SDK path does not exist.") 17 | endif() 18 | 19 | if(NOT IDA_DIR) 20 | message(WARNING "Path to IDA was not specified. " 21 | "Use -DIDA_DIR= if you want to install the plugin.") 22 | endif() 23 | 24 | # Build parameters. 25 | if(MSVC) # Windows 26 | # Disable warnings (there are too many of them, including warnings from 27 | # third-party libraries, which cannot be selectively disabled when using MSVC). 28 | string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 29 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W0") 30 | 31 | # Disable the min() and max() macros to prevent errors when using e.g. 32 | # std::numeric_limits<...>::max() 33 | # (http://stackoverflow.com/questions/1904635/warning-c4003-and-errors-c2589-and-c2059-on-x-stdnumeric-limitsintmax). 34 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DNOMINMAX") 35 | elseif(UNIX) # Linux or macOS 36 | # Common options. 37 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") 38 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic") 39 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") 40 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") 41 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra") 42 | 43 | # Ignore the following warnings (they are not fixable). 44 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format") 45 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter") 46 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-non-virtual-dtor") 47 | 48 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") 49 | else() 50 | message(FATAL_ERROR "Unsupported system type: ${CMAKE_SYSTEM_NAME}") 51 | endif() 52 | 53 | # Global defines. 54 | add_definitions(-D__IDP__ -D__PLUGIN__ -DNO_OBSOLETE_FUNCS) 55 | add_definitions(-D__X64__) 56 | if(WIN32) 57 | add_definitions(-D__NT__) 58 | elseif(APPLE) 59 | add_definitions(-D__MAC__) 60 | elseif(UNIX) # APPLE is also UNIX, so it MUST be before this elseif(). 61 | add_definitions(-D__LINUX__) 62 | else() 63 | message(FATAL_ERROR "Unsupported system type: ${CMAKE_SYSTEM_NAME}") 64 | endif() 65 | 66 | # IDA SDK libs. 67 | if(WIN32) 68 | set(idasdk_ea32 "${IDA_SDK_DIR}/lib/x64_win_vc_32/ida.lib") 69 | set(idasdk_ea64 "${IDA_SDK_DIR}/lib/x64_win_vc_64/ida.lib") 70 | elseif(APPLE) 71 | set(idasdk_ea32 "${IDA_SDK_DIR}/lib/x64_mac_gcc_32/libida.dylib") 72 | set(idasdk_ea64 "${IDA_SDK_DIR}/lib/x64_mac_gcc_64/libida64.dylib") 73 | elseif(UNIX) # APPLE is also UNIX, so it MUST be before this elseif(). 74 | set(idasdk_ea32 "${IDA_SDK_DIR}/lib/x64_linux_gcc_32/libida.so") 75 | set(idasdk_ea64 "${IDA_SDK_DIR}/lib/x64_linux_gcc_64/libida64.so") 76 | else() 77 | message(FATAL_ERROR "Unsupported system type: ${CMAKE_SYSTEM_NAME}") 78 | endif() 79 | 80 | # Includes. 81 | include_directories(SYSTEM 82 | "${IDA_SDK_DIR}/include" # Make IDA SDK includes work. 83 | ) 84 | 85 | # Sources 86 | set(DEMO_SOURCES 87 | context.cpp 88 | decompiler.cpp 89 | function.cpp 90 | demo.cpp 91 | place.cpp 92 | token.cpp 93 | ui.cpp 94 | yx.cpp 95 | ) 96 | 97 | # Libs. 98 | add_library(demo32 SHARED ${DEMO_SOURCES}) 99 | add_library(demo64 SHARED ${DEMO_SOURCES}) 100 | 101 | target_compile_definitions(demo64 PUBLIC __EA64__) 102 | 103 | target_link_libraries(demo32 jsoncpp ${idasdk_ea32}) 104 | target_link_libraries(demo64 jsoncpp ${idasdk_ea64}) 105 | 106 | if(MSYS) 107 | target_link_libraries(demo32 ws2_32) 108 | target_link_libraries(demo64 ws2_32) 109 | endif() 110 | 111 | set_target_properties(demo32 PROPERTIES PREFIX "") 112 | set_target_properties(demo64 PROPERTIES PREFIX "") 113 | set_target_properties(demo32 PROPERTIES OUTPUT_NAME "hexrays-demo") 114 | set_target_properties(demo64 PROPERTIES OUTPUT_NAME "hexrays-demo64") 115 | 116 | # Installation. 117 | if(IDA_DIR) 118 | install(TARGETS demo32 demo64 119 | LIBRARY DESTINATION "${IDA_DIR}/plugins/" 120 | RUNTIME DESTINATION "${IDA_DIR}/plugins/" 121 | ) 122 | endif() 123 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Avast 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hex-Rays demo 2 | 3 | This repository demonstrates how to use the extensive, but often not self-evident, functionality provided by [IDA SDK](https://www.hex-rays.com/products/ida/support/sdkdoc/index.html) in order to put together a plugin with Hex-Rays-like capabilities. 4 | 5 | Read the accompanying [article](https://engineering.avast.io/magic-probably-behind-hex-rays/) for more details. 6 | 7 | # Build and Installation 8 | 9 | This will build the plugin using the specified IDA SDK (7.3+), and install it into the specified IDA: 10 | ``` 11 | mkdir build 12 | cd build 13 | cmake .. -DIDA_SDK_DIR= -DIDA_DIR= 14 | make 15 | make install 16 | ``` 17 | 18 | # Run 19 | 20 | This is a demonstration plugin which works only with the provided `ack.x86.gcc.O0.g.elf` binary: 21 | 1. Build and install the plugin. 22 | 2. Open the `ack.x86.gcc.O0.g.elf` binary in IDA. 23 | 3. Navigate to `main() @ 0x8048577` or `ack() @ 0x804851C` and hit `Ctrl+Shift+D` to trigger the plugin. The function gets decompiled and you can interact with the result the same way you would in Hex-Rays. Only decompilation of these two functions is implemented. 24 | 25 | ![](doc/master.gif) 26 | -------------------------------------------------------------------------------- /ack.x86.gcc.O0.g.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avast/hexrays-demo/bbe8d6c3ddb9041856bafcd62a2ec0010b5a93ac/ack.x86.gcc.O0.g.elf -------------------------------------------------------------------------------- /context.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "context.h" 6 | 7 | int demo_msg(const char *format, ...) 8 | { 9 | static unsigned msgCntr = 0; 10 | 11 | va_list va; 12 | va_start(va, format); 13 | std::stringstream ss; 14 | ss << "demo #" << std::setw(5) << std::left << msgCntr++ 15 | << " -- " << format; 16 | auto ret = vmsg(ss.str().c_str(), va); 17 | va_end(va); 18 | return ret; 19 | } 20 | -------------------------------------------------------------------------------- /context.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HEXRAYS_DEMO_CONTEXT_H 3 | #define HEXRAYS_DEMO_CONTEXT_H 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "function.h" 14 | #include "ui.h" 15 | 16 | /** 17 | * Plugin's info messages. 18 | */ 19 | int demo_msg(const char *format, ...); 20 | 21 | /** 22 | * Plugin's global data. 23 | */ 24 | class Context : public plugmod_t, public event_listener_t 25 | { 26 | // Inherited. 27 | // 28 | public: 29 | virtual bool idaapi run(size_t) override; 30 | virtual ssize_t idaapi on_event(ssize_t code, va_list va) override; 31 | 32 | Context(); 33 | virtual ~Context(); 34 | 35 | // Actions. 36 | // 37 | public: 38 | function_ctx_ah_t function_ctx_ah; 39 | const action_desc_t function_ctx_ah_desc = ACTION_DESC_LITERAL_PLUGMOD( 40 | function_ctx_ah_t::actionName, 41 | function_ctx_ah_t::actionLabel, 42 | &function_ctx_ah, 43 | this, 44 | function_ctx_ah_t::actionHotkey, 45 | nullptr, 46 | -1 47 | ); 48 | 49 | variable_ctx_ah_t variable_ctx_ah; 50 | const action_desc_t variable_ctx_ah_desc = ACTION_DESC_LITERAL_PLUGMOD( 51 | variable_ctx_ah_t::actionName, 52 | variable_ctx_ah_t::actionLabel, 53 | &variable_ctx_ah, 54 | this, 55 | variable_ctx_ah_t::actionHotkey, 56 | nullptr, 57 | -1 58 | ); 59 | 60 | copy2asm_ah_t copy2asm_ah = copy2asm_ah_t(*this); 61 | const action_desc_t copy2asm_ah_desc = ACTION_DESC_LITERAL_PLUGMOD( 62 | copy2asm_ah_t::actionName, 63 | copy2asm_ah_t::actionLabel, 64 | ©2asm_ah, 65 | this, 66 | copy2asm_ah_t::actionHotkey, 67 | nullptr, 68 | -1 69 | ); 70 | 71 | // Context data. 72 | // 73 | public: 74 | TWidget* custViewer = nullptr; 75 | TWidget* codeViewer = nullptr; 76 | /// Currently displayed function. 77 | Function* fnc = nullptr; 78 | // Color used by view synchronization. 79 | bgcolor_t syncColor = 0x90ee90; 80 | }; 81 | 82 | #endif -------------------------------------------------------------------------------- /decompiler.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "context.h" 5 | #include "decompiler.h" 6 | 7 | /** 8 | * Representation of function: 9 | * int __cdecl ack(int a1, int a2) 10 | * { 11 | * int v3; // eax 12 | * 13 | * if ( !m ) 14 | * return n + 1; 15 | * if ( !n ) 16 | * return ack(m - 1, 1); 17 | * v3 = ack(m, n - 1); 18 | * return ack(m - 1, v3); 19 | * } 20 | */ 21 | Function fnc_ack = 22 | { 23 | "ack", 24 | 0x804851C, 25 | 0x8048577, 26 | { 27 | // int __cdecl ack(int a1, int a2) 28 | {Token::Kind::TYPE, 0x804851C, "int"}, 29 | {Token::Kind::WHITE_SPACE, 0x804851C, " "}, 30 | {Token::Kind::LITERAL_SYM, 0x804851C, "__cdecl"}, 31 | {Token::Kind::WHITE_SPACE, 0x804851C, " "}, 32 | {Token::Kind::ID_FNC, 0x804851C, "ack"}, 33 | {Token::Kind::PUNCTUATION, 0x804851C, "("}, 34 | {Token::Kind::TYPE, 0x804851C, "int"}, 35 | {Token::Kind::WHITE_SPACE, 0x804851C, " "}, 36 | {Token::Kind::ID_ARG, 0x804851C, "m"}, 37 | {Token::Kind::PUNCTUATION, 0x804851C, ","}, 38 | {Token::Kind::WHITE_SPACE, 0x804851C, " "}, 39 | {Token::Kind::TYPE, 0x804851C, "int"}, 40 | {Token::Kind::WHITE_SPACE, 0x804851C, " "}, 41 | {Token::Kind::ID_ARG, 0x804851C, "n"}, 42 | {Token::Kind::PUNCTUATION, 0x804851C, ")"}, 43 | {Token::Kind::NEW_LINE, 0x804851C, "\n"}, 44 | // { 45 | {Token::Kind::PUNCTUATION, 0x804851C, "{"}, 46 | {Token::Kind::NEW_LINE, 0x804851C, "\n"}, 47 | // int v3; // eax 48 | {Token::Kind::WHITE_SPACE, 0x804851C, " "}, 49 | {Token::Kind::TYPE, 0x804851C, "int"}, 50 | {Token::Kind::WHITE_SPACE, 0x804851C, " "}, 51 | {Token::Kind::ID_VAR, 0x804851C, "v3"}, 52 | {Token::Kind::PUNCTUATION, 0x804851C, ";"}, 53 | {Token::Kind::WHITE_SPACE, 0x804851C, " "}, 54 | {Token::Kind::COMMENT, 0x804851C, "// eax"}, 55 | {Token::Kind::NEW_LINE, 0x804851C, "\n"}, 56 | // 57 | {Token::Kind::NEW_LINE, 0x804851C, "\n"}, 58 | // if ( !m ) 59 | {Token::Kind::WHITE_SPACE, 0x8048526, " "}, 60 | {Token::Kind::KEYWORD, 0x8048526, "if"}, 61 | {Token::Kind::WHITE_SPACE, 0x8048526, " "}, 62 | {Token::Kind::PUNCTUATION, 0x8048526, "("}, 63 | {Token::Kind::WHITE_SPACE, 0x8048526, " "}, 64 | {Token::Kind::OPERATOR, 0x8048526, "!"}, 65 | {Token::Kind::ID_ARG, 0x8048526, "m"}, 66 | {Token::Kind::WHITE_SPACE, 0x8048526, " "}, 67 | {Token::Kind::PUNCTUATION, 0x8048526, ")"}, 68 | {Token::Kind::NEW_LINE, 0x8048526, "\n"}, 69 | // return n + 1; 70 | {Token::Kind::WHITE_SPACE, 0x804852B, " "}, 71 | {Token::Kind::KEYWORD, 0x804852B, "return"}, 72 | {Token::Kind::WHITE_SPACE, 0x804852B, " "}, 73 | {Token::Kind::ID_ARG, 0x804852B, "n"}, 74 | {Token::Kind::WHITE_SPACE, 0x804852B, " "}, 75 | {Token::Kind::OPERATOR, 0x804852B, "+"}, 76 | {Token::Kind::WHITE_SPACE, 0x804852B, " "}, 77 | {Token::Kind::LITERAL_INT, 0x804852B, "1"}, 78 | {Token::Kind::PUNCTUATION, 0x804852B, ";"}, 79 | {Token::Kind::NEW_LINE, 0x804852B, "\n"}, 80 | // if ( !n ) 81 | {Token::Kind::WHITE_SPACE, 0x8048534, " "}, 82 | {Token::Kind::KEYWORD, 0x8048534, "if"}, 83 | {Token::Kind::WHITE_SPACE, 0x8048534, " "}, 84 | {Token::Kind::PUNCTUATION, 0x8048534, "("}, 85 | {Token::Kind::WHITE_SPACE, 0x8048534, " "}, 86 | {Token::Kind::OPERATOR, 0x8048534, "!"}, 87 | {Token::Kind::ID_ARG, 0x8048534, "n"}, 88 | {Token::Kind::WHITE_SPACE, 0x8048534, " "}, 89 | {Token::Kind::PUNCTUATION, 0x8048534, ")"}, 90 | {Token::Kind::NEW_LINE, 0x8048534, "\n"}, 91 | // return ack(m - 1, 1); 92 | {Token::Kind::WHITE_SPACE, 0x8048547, " "}, 93 | {Token::Kind::KEYWORD, 0x8048547, "return"}, 94 | {Token::Kind::WHITE_SPACE, 0x8048547, " "}, 95 | {Token::Kind::ID_FNC, 0x8048547, "ack"}, 96 | {Token::Kind::PUNCTUATION, 0x8048547, "("}, 97 | {Token::Kind::ID_ARG, 0x8048544, "m"}, 98 | {Token::Kind::WHITE_SPACE, 0x8048544, " "}, 99 | {Token::Kind::OPERATOR, 0x8048544, "-"}, 100 | {Token::Kind::WHITE_SPACE, 0x8048544, " "}, 101 | {Token::Kind::LITERAL_INT, 0x8048539, "1"}, 102 | {Token::Kind::PUNCTUATION, 0x8048539, ","}, 103 | {Token::Kind::WHITE_SPACE, 0x8048539, " "}, 104 | {Token::Kind::LITERAL_INT, 0x804853C, "1"}, 105 | {Token::Kind::PUNCTUATION, 0x804853C, ")"}, 106 | {Token::Kind::PUNCTUATION, 0x804853C, ";"}, 107 | {Token::Kind::NEW_LINE, 0x804853C, "\n"}, 108 | // v3 = ack(m, n - 1); 109 | {Token::Kind::WHITE_SPACE, 0x804855E, " "}, 110 | {Token::Kind::ID_VAR, 0x804855E, "v3"}, 111 | {Token::Kind::WHITE_SPACE, 0x804855E, " "}, 112 | {Token::Kind::OPERATOR, 0x804855E, "="}, 113 | {Token::Kind::WHITE_SPACE, 0x804855E, " "}, 114 | {Token::Kind::ID_FNC, 0x804855E, "ack"}, 115 | {Token::Kind::PUNCTUATION, 0x804855E, "("}, 116 | {Token::Kind::ID_ARG, 0x804855B, "m"}, 117 | {Token::Kind::PUNCTUATION, 0x804855B, ","}, 118 | {Token::Kind::WHITE_SPACE, 0x804855B, " "}, 119 | {Token::Kind::ID_ARG, 0x8048554, "n"}, 120 | {Token::Kind::WHITE_SPACE, 0x8048554, " "}, 121 | {Token::Kind::OPERATOR, 0x8048554, "-"}, 122 | {Token::Kind::WHITE_SPACE, 0x8048554, " "}, 123 | {Token::Kind::LITERAL_INT, 0x8048551, "1"}, 124 | {Token::Kind::PUNCTUATION, 0x8048551, ")"}, 125 | {Token::Kind::PUNCTUATION, 0x8048551, ";"}, 126 | {Token::Kind::NEW_LINE, 0x8048551, "\n"}, 127 | // return ack(m - 1, v3); 128 | {Token::Kind::WHITE_SPACE, 0x8048575, " "}, 129 | {Token::Kind::KEYWORD, 0x8048575, "return"}, 130 | {Token::Kind::WHITE_SPACE, 0x8048575, " "}, 131 | {Token::Kind::ID_FNC, 0x8048570, "ack"}, 132 | {Token::Kind::PUNCTUATION, 0x8048570, "("}, 133 | {Token::Kind::ID_ARG, 0x804856D, "m"}, 134 | {Token::Kind::WHITE_SPACE, 0x804856D, " "}, 135 | {Token::Kind::OPERATOR, 0x804856D, "-"}, 136 | {Token::Kind::WHITE_SPACE, 0x804856D, " "}, 137 | {Token::Kind::LITERAL_INT, 0x8048566, "1"}, 138 | {Token::Kind::PUNCTUATION, 0x8048566, ","}, 139 | {Token::Kind::WHITE_SPACE, 0x8048566, " "}, 140 | {Token::Kind::ID_VAR, 0x8048569, "v3"}, 141 | {Token::Kind::PUNCTUATION, 0x8048569, ")"}, 142 | {Token::Kind::PUNCTUATION, 0x8048569, ";"}, 143 | {Token::Kind::NEW_LINE, 0x8048569, "\n"}, 144 | // } 145 | {Token::Kind::PUNCTUATION, 0x8048576, "}"}, 146 | // Do not add new line at the end. 147 | // For whatever reason, the last line is not displayed if the max 148 | // place's X coordinate isn't 0. 149 | // So always put only one token at the last line. 150 | } 151 | }; 152 | 153 | /** 154 | * Representation of function: 155 | * int __cdecl main(int argc, const char **argv, const char **envp) 156 | * { 157 | * int v4; // [esp+14h] [ebp-Ch] 158 | * int v5; // [esp+18h] [ebp-8h] 159 | * int v6; // [esp+1Ch] [ebp-4h] 160 | * 161 | * v6 = 0; 162 | * v5 = 0; 163 | * v4 = 0; 164 | * __isoc99_scanf(\"%d %d\", &v5, &v4); 165 | * v6 = ack(v5, v4); 166 | * printf("ackerman( %d , %d ) = %d\n", v5, v4, v6); 167 | * return v6; 168 | * } 169 | */ 170 | Function fnc_main = 171 | { 172 | "main", 173 | 0x8048577, 174 | 0x80485F6, 175 | { 176 | // int __cdecl main(int argc, const char **argv, const char **envp) 177 | {Token::Kind::TYPE, 0x8048577, "int"}, 178 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 179 | {Token::Kind::LITERAL_SYM, 0x8048577, "__cdecl"}, 180 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 181 | {Token::Kind::ID_FNC, 0x8048577, "main"}, 182 | {Token::Kind::PUNCTUATION, 0x8048577, "("}, 183 | {Token::Kind::TYPE, 0x8048577, "int"}, 184 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 185 | {Token::Kind::ID_ARG, 0x8048577, "argc"}, 186 | {Token::Kind::PUNCTUATION, 0x8048577, ","}, 187 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 188 | {Token::Kind::TYPE, 0x8048577, "const char"}, 189 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 190 | {Token::Kind::OPERATOR, 0x8048577, "*"}, 191 | {Token::Kind::OPERATOR, 0x8048577, "*"}, 192 | {Token::Kind::ID_ARG, 0x8048577, "argv"}, 193 | {Token::Kind::PUNCTUATION, 0x8048577, ","}, 194 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 195 | {Token::Kind::TYPE, 0x8048577, "const char"}, 196 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 197 | {Token::Kind::OPERATOR, 0x8048577, "*"}, 198 | {Token::Kind::OPERATOR, 0x8048577, "*"}, 199 | {Token::Kind::ID_ARG, 0x8048577, "envp"}, 200 | {Token::Kind::PUNCTUATION, 0x8048577, ")"}, 201 | {Token::Kind::NEW_LINE, 0x8048577, "\n"}, 202 | // { 203 | {Token::Kind::PUNCTUATION, 0x8048577, "{"}, 204 | {Token::Kind::NEW_LINE, 0x8048577, "\n"}, 205 | // int v4; // [esp+14h] [ebp-Ch] 206 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 207 | {Token::Kind::TYPE, 0x8048577, "int"}, 208 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 209 | {Token::Kind::ID_VAR, 0x8048577, "v4"}, 210 | {Token::Kind::PUNCTUATION, 0x8048577, ";"}, 211 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 212 | {Token::Kind::COMMENT, 0x8048577, "// [esp+14h] [ebp-Ch]"}, 213 | {Token::Kind::NEW_LINE, 0x8048577, "\n"}, 214 | // int v5; // [esp+18h] [ebp-8h] 215 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 216 | {Token::Kind::TYPE, 0x8048577, "int"}, 217 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 218 | {Token::Kind::ID_VAR, 0x8048577, "v5"}, 219 | {Token::Kind::PUNCTUATION, 0x8048577, ";"}, 220 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 221 | {Token::Kind::COMMENT, 0x8048577, "// [esp+18h] [ebp-8h]"}, 222 | {Token::Kind::NEW_LINE, 0x8048577, "\n"}, 223 | // int v6; // [esp+1Ch] [ebp-4h] 224 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 225 | {Token::Kind::TYPE, 0x8048577, "int"}, 226 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 227 | {Token::Kind::ID_VAR, 0x8048577, "v6"}, 228 | {Token::Kind::PUNCTUATION, 0x8048577, ";"}, 229 | {Token::Kind::WHITE_SPACE, 0x8048577, " "}, 230 | {Token::Kind::COMMENT, 0x8048577, "// [esp+1Ch] [ebp-4h]"}, 231 | {Token::Kind::NEW_LINE, 0x8048577, "\n"}, 232 | // 233 | {Token::Kind::NEW_LINE, 0x8048577, "\n"}, 234 | // v6 = 0; 235 | {Token::Kind::WHITE_SPACE, 0x8048580, " "}, 236 | {Token::Kind::ID_VAR, 0x8048580, "v6"}, 237 | {Token::Kind::WHITE_SPACE, 0x8048580, " "}, 238 | {Token::Kind::OPERATOR, 0x8048580, "="}, 239 | {Token::Kind::WHITE_SPACE, 0x8048580, " "}, 240 | {Token::Kind::LITERAL_INT, 0x8048580, "0"}, 241 | {Token::Kind::PUNCTUATION, 0x8048580, ";"}, 242 | {Token::Kind::NEW_LINE, 0x8048580, "\n"}, 243 | // v5 = 0; 244 | {Token::Kind::WHITE_SPACE, 0x8048588, " "}, 245 | {Token::Kind::ID_VAR, 0x8048588, "v5"}, 246 | {Token::Kind::WHITE_SPACE, 0x8048588, " "}, 247 | {Token::Kind::OPERATOR, 0x8048588, "="}, 248 | {Token::Kind::WHITE_SPACE, 0x8048588, " "}, 249 | {Token::Kind::LITERAL_INT, 0x8048588, "0"}, 250 | {Token::Kind::PUNCTUATION, 0x8048588, ";"}, 251 | {Token::Kind::NEW_LINE, 0x8048588, "\n"}, 252 | // v4 = 0; 253 | {Token::Kind::WHITE_SPACE, 0x8048590, " "}, 254 | {Token::Kind::ID_VAR, 0x8048590, "v4"}, 255 | {Token::Kind::WHITE_SPACE, 0x8048590, " "}, 256 | {Token::Kind::OPERATOR, 0x8048590, "="}, 257 | {Token::Kind::WHITE_SPACE, 0x8048590, " "}, 258 | {Token::Kind::LITERAL_INT, 0x8048590, "0"}, 259 | {Token::Kind::PUNCTUATION, 0x8048590, ";"}, 260 | {Token::Kind::NEW_LINE, 0x8048590, "\n"}, 261 | // __isoc99_scanf(\"%d %d\", &v5, &v4); 262 | {Token::Kind::WHITE_SPACE, 0x80485AF, " "}, 263 | {Token::Kind::ID_FNC, 0x80485AF, "__isoc99_scanf"}, 264 | {Token::Kind::PUNCTUATION, 0x80485AF, "("}, 265 | {Token::Kind::LITERAL_STR, 0x80485A8, "\"%d %d\""}, 266 | {Token::Kind::PUNCTUATION, 0x80485A8, ","}, 267 | {Token::Kind::WHITE_SPACE, 0x80485A8, " "}, 268 | {Token::Kind::OPERATOR, 0x80485A4, "&"}, 269 | {Token::Kind::ID_VAR, 0x80485A4, "v5"}, 270 | {Token::Kind::PUNCTUATION, 0x80485A4, ","}, 271 | {Token::Kind::WHITE_SPACE, 0x80485A4, " "}, 272 | {Token::Kind::OPERATOR, 0x804859C, "&"}, 273 | {Token::Kind::ID_VAR, 0x804859C, "v4"}, 274 | {Token::Kind::PUNCTUATION, 0x804859C, ")"}, 275 | {Token::Kind::PUNCTUATION, 0x804859C, ";"}, 276 | {Token::Kind::NEW_LINE, 0x804859C, "\n"}, 277 | // v6 = ack(v5, v4); 278 | {Token::Kind::WHITE_SPACE, 0x80485C8, " "}, 279 | {Token::Kind::ID_VAR, 0x80485C8, "v6"}, 280 | {Token::Kind::WHITE_SPACE, 0x80485C8, " "}, 281 | {Token::Kind::OPERATOR, 0x80485C8, "="}, 282 | {Token::Kind::WHITE_SPACE, 0x80485C8, " "}, 283 | {Token::Kind::ID_FNC, 0x80485C3, "ack"}, 284 | {Token::Kind::PUNCTUATION, 0x80485C3, "("}, 285 | {Token::Kind::ID_VAR, 0x80485C0, "v5"}, 286 | {Token::Kind::PUNCTUATION, 0x80485C0, ","}, 287 | {Token::Kind::WHITE_SPACE, 0x80485C0, " "}, 288 | {Token::Kind::ID_VAR, 0x80485BC, "v4"}, 289 | {Token::Kind::PUNCTUATION, 0x80485BC, ")"}, 290 | {Token::Kind::PUNCTUATION, 0x80485BC, ";"}, 291 | {Token::Kind::NEW_LINE, 0x80485BC, "\n"}, 292 | // printf("ackerman( %d , %d ) = %d\n", v5, v4, v6); 293 | {Token::Kind::WHITE_SPACE, 0x80485EB, " "}, 294 | {Token::Kind::ID_FNC, 0x80485EB, "printf"}, 295 | {Token::Kind::PUNCTUATION, 0x80485EB, "("}, 296 | {Token::Kind::LITERAL_STR, 0x80485E4, "\"ackerman( %d , %d ) = %d\\n\""}, 297 | {Token::Kind::PUNCTUATION, 0x80485E4, ","}, 298 | {Token::Kind::WHITE_SPACE, 0x80485E4, " "}, 299 | {Token::Kind::ID_VAR, 0x80485E0, "v5"}, 300 | {Token::Kind::PUNCTUATION, 0x80485E0, ","}, 301 | {Token::Kind::WHITE_SPACE, 0x80485E0, " "}, 302 | {Token::Kind::ID_VAR, 0x80485DC, "v4"}, 303 | {Token::Kind::PUNCTUATION, 0x80485DC, ","}, 304 | {Token::Kind::WHITE_SPACE, 0x80485DC, " "}, 305 | {Token::Kind::ID_VAR, 0x80485D8, "v6"}, 306 | {Token::Kind::PUNCTUATION, 0x80485D8, ")"}, 307 | {Token::Kind::PUNCTUATION, 0x80485D8, ";"}, 308 | {Token::Kind::NEW_LINE, 0x80485D8, "\n"}, 309 | // return v6; 310 | {Token::Kind::WHITE_SPACE, 0x80485F4, " "}, 311 | {Token::Kind::KEYWORD, 0x80485F4, "return"}, 312 | {Token::Kind::WHITE_SPACE, 0x80485F4, " "}, 313 | {Token::Kind::ID_VAR, 0x80485F4, "v6"}, 314 | {Token::Kind::PUNCTUATION, 0x80485F4, ";"}, 315 | {Token::Kind::NEW_LINE, 0x80485F4, "\n"}, 316 | // } 317 | {Token::Kind::PUNCTUATION, 0x80485F5, "}"}, 318 | // Do not add new line at the end. 319 | // For whatever reason, the last line is not displayed if the max 320 | // place's X coordinate isn't 0. 321 | // So always put only one token at the last line. 322 | } 323 | }; 324 | 325 | using Functions = std::map; 326 | Functions functions = { 327 | {fnc_ack.getStart(), fnc_ack}, 328 | {fnc_main.getStart(), fnc_main} 329 | }; 330 | 331 | Function* Decompiler::decompile(ea_t ea) 332 | { 333 | Function* ret = nullptr; 334 | auto it = functions.upper_bound(ea); 335 | 336 | if (it == functions.begin()) 337 | { 338 | // Before the first -> no function. 339 | ret = nullptr; 340 | } 341 | else if (it == functions.end() && !functions.empty()) 342 | { 343 | // After the last -> check the last function. 344 | auto& last = functions.rbegin()->second; 345 | ret = last.ea_inside(ea) ? &last : nullptr; 346 | } 347 | else if (it != functions.end()) 348 | { 349 | // In the middle -> check the previous. 350 | --it; 351 | auto& prev = it->second; 352 | ret = prev.ea_inside(ea) ? &prev : nullptr; 353 | } 354 | 355 | if (ret) 356 | { 357 | demo_msg("Decompiler::decompile(%a): %s\n", 358 | ea, 359 | ret->toString().c_str() 360 | ); 361 | } 362 | else 363 | { 364 | demo_msg("Decompiler::decompile(%a): cannot decompile\n", ea); 365 | } 366 | 367 | return ret; 368 | } 369 | -------------------------------------------------------------------------------- /decompiler.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HEXRAYS_DEMO_DECOMPILER_H 3 | #define HEXRAYS_DEMO_DECOMPILER_H 4 | 5 | #include 6 | 7 | #include "function.h" 8 | 9 | /** 10 | * Decomiler mock. 11 | */ 12 | class Decompiler 13 | { 14 | public: 15 | /// Decompile the function contianing the given \p ea. 16 | static Function* decompile(ea_t ea); 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /demo.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "context.h" 3 | #include "decompiler.h" 4 | #include "function.h" 5 | #include "place.h" 6 | #include "ui.h" 7 | 8 | plugmod_t* idaapi init(void) 9 | { 10 | demo_msg("init()\n"); 11 | return new Context(); 12 | } 13 | 14 | Context::Context() 15 | { 16 | register_action(function_ctx_ah_desc); 17 | register_action(variable_ctx_ah_desc); 18 | register_action(copy2asm_ah_desc); 19 | } 20 | 21 | bool idaapi Context::run(size_t) 22 | { 23 | demo_place_t::registerPlace(PLUGIN); 24 | hook_event_listener(HT_UI, this); 25 | 26 | ea_t ea = get_screen_ea(); 27 | fnc = Decompiler::decompile(ea); 28 | if (fnc == nullptr) 29 | { 30 | warning("[demo] cannot decompile function @ %a\n", ea); 31 | return true; 32 | } 33 | 34 | demo_place_t min(fnc, fnc->min_yx()); 35 | demo_place_t max(fnc, fnc->max_yx()); 36 | demo_place_t cur(fnc, fnc->ea_2_yx(ea)); 37 | 38 | static const char title[] = "hexrays demo"; 39 | TWidget* widget = find_widget(title); 40 | if (widget != nullptr) 41 | { 42 | demo_msg("run(%a): switching existing viewer to %s\n", 43 | ea, 44 | fnc->toString().c_str() 45 | ); 46 | 47 | set_custom_viewer_range(custViewer, &min, &max); 48 | jumpto(custViewer, &cur, cur.x(), cur.y()); 49 | bool take_focus = true; 50 | activate_widget(custViewer, take_focus); 51 | return true; 52 | } 53 | 54 | demo_msg("run(%a): creating new viewer for %s\n", 55 | ea, 56 | fnc->toString().c_str() 57 | ); 58 | 59 | // Without setting both x and y in render info, the current line gets 60 | // displayed as the first line in the viewer. Which is not nice because we 61 | // don't see the context before it. It is better if it is somewhere in the 62 | // middle of the viewer. 63 | renderer_info_t rinfo; 64 | rinfo.rtype = TCCRT_FLAT; 65 | rinfo.pos.cx = cur.x(); 66 | rinfo.pos.cy = cur.y(); 67 | 68 | custViewer = create_custom_viewer( 69 | title, // title 70 | &min, // minplace 71 | &max, // maxplace 72 | &cur, // curplace 73 | &rinfo, // rinfo 74 | this, // ud 75 | &ui_handlers, // handlers 76 | this, // cvhandlers_ud 77 | nullptr // parent widget 78 | ); 79 | set_view_renderer_type(custViewer, TCCRT_FLAT); 80 | 81 | codeViewer = create_code_viewer(custViewer); 82 | set_code_viewer_is_source(codeViewer); 83 | display_widget(codeViewer, WOPN_DP_TAB | WOPN_RESTORE); 84 | 85 | return true; 86 | } 87 | 88 | Context::~Context() 89 | { 90 | demo_msg("term()\n"); 91 | 92 | unhook_event_listener(HT_UI, this); 93 | unregister_action(function_ctx_ah_desc.name); 94 | unregister_action(variable_ctx_ah_desc.name); 95 | unregister_action(copy2asm_ah_desc.name); 96 | } 97 | 98 | plugin_t PLUGIN = 99 | { 100 | IDP_INTERFACE_VERSION, 101 | PLUGIN_MULTI, // plugin flags 102 | init, // initialize fnc 103 | nullptr, // terminate fnc 104 | nullptr, // invoke fnc 105 | "hexrays demo", // long plugin comment 106 | "hexrays demo", // multiline plugin help 107 | "hexrays demo", // the preferred plugin short name 108 | "Ctrl-Shift-D" // the preferred plugin hotkey 109 | }; 110 | -------------------------------------------------------------------------------- /doc/master.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avast/hexrays-demo/bbe8d6c3ddb9041856bafcd62a2ec0010b5a93ac/doc/master.gif -------------------------------------------------------------------------------- /function.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "function.h" 5 | 6 | Function::Function( 7 | const std::string& name, 8 | ea_t start, 9 | ea_t end, 10 | const std::vector& tokens) 11 | : _name(name) 12 | , _start(start) 13 | , _end(end) 14 | { 15 | std::size_t y = YX::starting_y; 16 | std::size_t x = YX::starting_x; 17 | for (auto& t : tokens) 18 | { 19 | _tokens[YX(y, x)] = t; 20 | 21 | if (_ea2yx.count(t.ea) == 0) 22 | { 23 | _ea2yx[t.ea] = YX(y, x); 24 | } 25 | 26 | if (t.kind == Token::Kind::NEW_LINE) 27 | { 28 | ++y; 29 | x = YX::starting_x; 30 | } 31 | else 32 | { 33 | x += t.value.size(); 34 | } 35 | } 36 | } 37 | 38 | const std::string& Function::getName() const 39 | { 40 | return _name; 41 | } 42 | 43 | ea_t Function::getStart() const 44 | { 45 | return _start; 46 | } 47 | 48 | ea_t Function::getEnd() const 49 | { 50 | return _end; 51 | } 52 | 53 | const Token* Function::getToken(YX yx) const 54 | { 55 | auto it = _tokens.find(adjust_yx(yx)); 56 | return it == _tokens.end() ? nullptr : &it->second; 57 | } 58 | 59 | YX Function::min_yx() const 60 | { 61 | return _tokens.empty() ? YX::starting_yx : _tokens.begin()->first; 62 | } 63 | 64 | YX Function::max_yx() const 65 | { 66 | return _tokens.empty() ? YX::starting_yx : _tokens.rbegin()->first; 67 | } 68 | 69 | YX Function::prev_yx(YX yx) const 70 | { 71 | auto it = _tokens.find(adjust_yx(yx)); 72 | if (it == _tokens.end() || it == _tokens.begin()) 73 | { 74 | return yx; 75 | } 76 | --it; 77 | return it->first; 78 | } 79 | 80 | YX Function::next_yx(YX yx) const 81 | { 82 | auto it = _tokens.find(adjust_yx(yx)); 83 | auto nit = it; 84 | ++nit; 85 | if (it == _tokens.end() || nit == _tokens.end()) 86 | { 87 | return yx; 88 | } 89 | return nit->first; 90 | } 91 | 92 | YX Function::adjust_yx(YX yx) const 93 | { 94 | if (_tokens.empty() || _tokens.count(yx)) 95 | { 96 | return yx; 97 | } 98 | if (yx <= min_yx()) 99 | { 100 | return min_yx(); 101 | } 102 | if (yx >= max_yx()) 103 | { 104 | return max_yx(); 105 | } 106 | 107 | auto it = _tokens.upper_bound(yx); 108 | --it; 109 | return it->first; 110 | } 111 | 112 | std::string Function::line_yx(YX yx) const 113 | { 114 | std::string line; 115 | 116 | auto it = _tokens.find(adjust_yx(yx)); 117 | while (it != _tokens.end() 118 | && it->first.y == yx.y 119 | && it->second.kind != Token::Kind::NEW_LINE) 120 | { 121 | line += std::string(SCOLOR_ON) 122 | + it->second.getColorTag() 123 | + it->second.value 124 | + SCOLOR_OFF 125 | + it->second.getColorTag(); 126 | ++it; 127 | } 128 | 129 | return line; 130 | } 131 | 132 | ea_t Function::yx_2_ea(YX yx) const 133 | { 134 | auto it = _tokens.find(adjust_yx(yx)); 135 | if (it == _tokens.end()) 136 | { 137 | return BADADDR; 138 | } 139 | return it->second.ea; 140 | } 141 | 142 | std::set Function::yx_2_eas(YX yx) const 143 | { 144 | std::set ret; 145 | auto it = _tokens.find(YX(yx.y, 0)); 146 | while (it != _tokens.end() && it->first.y == yx.y) 147 | { 148 | ret.insert(it->second.ea); 149 | ++it; 150 | } 151 | return ret; 152 | } 153 | 154 | YX Function::ea_2_yx(ea_t ea) const 155 | { 156 | if (_ea2yx.empty()) 157 | { 158 | return YX::starting_yx; 159 | } 160 | if (ea < _ea2yx.begin()->first || _ea2yx.rbegin()->first < ea) 161 | { 162 | return YX::starting_yx; 163 | } 164 | if (ea == _ea2yx.rbegin()->first) 165 | { 166 | return max_yx(); 167 | } 168 | 169 | auto it = _ea2yx.upper_bound(ea); 170 | --it; 171 | return it->second; 172 | } 173 | 174 | bool Function::ea_inside(ea_t ea) const 175 | { 176 | return getStart() <= ea && ea < getEnd(); 177 | } 178 | 179 | std::vector> Function::toLines() const 180 | { 181 | std::vector> lines; 182 | 183 | ea_t addr = BADADDR; 184 | std::string line; 185 | for (auto& p : _tokens) 186 | { 187 | if (addr == BADADDR) 188 | { 189 | addr = p.second.ea; 190 | } 191 | 192 | auto& t = p.second; 193 | if (t.kind == Token::Kind::NEW_LINE) 194 | { 195 | lines.emplace_back(std::make_pair(line, addr)); 196 | line.clear(); 197 | addr = BADADDR; 198 | } 199 | else 200 | { 201 | line += t.value; 202 | } 203 | } 204 | 205 | return lines; 206 | } 207 | 208 | std::string Function::toString() const 209 | { 210 | std::stringstream ss; 211 | ss << *this; 212 | return ss.str(); 213 | } 214 | 215 | std::ostream& operator<<(std::ostream& os, const Function& f) 216 | { 217 | os << f.getName() << "<" << std::hex << f.getStart() 218 | << "," << f.getEnd() << ")"; 219 | return os; 220 | } 221 | -------------------------------------------------------------------------------- /function.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HEXRAYS_DEMO_FUNCTION_H 3 | #define HEXRAYS_DEMO_FUNCTION_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "token.h" 13 | #include "yx.h" 14 | 15 | /** 16 | * Decompiled function - i.e. its source code. 17 | * The object is XY-aware and EA-aware. 18 | */ 19 | class Function 20 | { 21 | public: 22 | Function( 23 | const std::string& name, 24 | ea_t start, 25 | ea_t end, 26 | const std::vector& tokens 27 | ); 28 | 29 | const std::string& getName() const; 30 | ea_t getStart() const; 31 | ea_t getEnd() const; 32 | /// Token at YX. 33 | const Token* getToken(YX yx) const; 34 | 35 | /// YX of the first token. 36 | YX min_yx() const; 37 | /// YX of the last token. 38 | YX max_yx() const; 39 | /// YX of the token before the token on the given YX. 40 | YX prev_yx(YX yx) const; 41 | /// YX of the token after the token on the given YX. 42 | YX next_yx(YX yx) const; 43 | /// [Starting] YX of the token which contains the given YX. 44 | YX adjust_yx(YX yx) const; 45 | /// Entire colored line containing the given YX. 46 | /// I.e. concatenation of all the tokens with y == yx.y 47 | std::string line_yx(YX yx) const; 48 | /// Address of the given YX. 49 | ea_t yx_2_ea(YX yx) const; 50 | /// Addresses of all the XYs with y == yx.y 51 | std::set yx_2_eas(YX yx) const; 52 | /// [The first] XY with the given address. 53 | YX ea_2_yx(ea_t ea) const; 54 | /// Is address inside this function? 55 | bool ea_inside(ea_t ea) const; 56 | 57 | /// Lines with associated addresses. 58 | std::vector> toLines() const; 59 | std::string toString() const; 60 | friend std::ostream& operator<<(std::ostream& os, const Function& f); 61 | 62 | private: 63 | std::string _name; 64 | ea_t _start; 65 | ea_t _end; 66 | std::map _tokens; 67 | /// Multiple YXs can be associated with the same address. 68 | /// This stores the first such XY. 69 | std::map _ea2yx; 70 | }; 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /place.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "context.h" 5 | #include "decompiler.h" 6 | #include "place.h" 7 | 8 | static const idaplace_t _idaplace; 9 | static const demo_place_t _template(nullptr, YX()); 10 | 11 | void idaapi demo_place_t::print(qstring* out_buf, void* ud) const 12 | { 13 | static unsigned cntr = 0; 14 | cntr++; 15 | 16 | qstring ea_str; 17 | ea2str(&ea_str, toea()); 18 | 19 | std::string str = std::string("hello @ ") 20 | + ea_str.c_str() 21 | + " @ " 22 | + std::to_string(y()) + ":" + std::to_string(x()) 23 | + " # " + std::to_string(cntr); 24 | *out_buf = str.c_str(); 25 | } 26 | 27 | uval_t idaapi demo_place_t::touval(void* ud) const 28 | { 29 | return y(); 30 | } 31 | 32 | place_t* idaapi demo_place_t::clone(void) const 33 | { 34 | return new demo_place_t(*this); 35 | } 36 | 37 | void idaapi demo_place_t::copyfrom(const place_t* from) 38 | { 39 | auto* p = static_cast(from); 40 | 41 | lnnum = p->lnnum; 42 | _fnc = p->_fnc; 43 | _yx = p->_yx; 44 | } 45 | 46 | place_t* idaapi demo_place_t::makeplace( 47 | void* ud, 48 | uval_t y, 49 | int lnnum) const 50 | { 51 | auto* p = new demo_place_t(_fnc, YX(y, 0)); 52 | p->lnnum = lnnum; 53 | return p; 54 | } 55 | 56 | int idaapi demo_place_t::compare(const place_t* t2) const 57 | { 58 | return compare2(t2, nullptr); 59 | } 60 | 61 | int idaapi demo_place_t::compare2(const place_t* t2, void *ud) const 62 | { 63 | auto* p = static_cast(t2); 64 | 65 | if (_fnc == p->_fnc) 66 | { 67 | if (yx() < p->yx()) return -1; 68 | else if (yx() > p->yx()) return 1; 69 | else return 0; 70 | } 71 | // I'm not sure if this can happen (i.e. places from different functions 72 | // are compared), but better safe than sorry. 73 | else if (_fnc->getStart() < p->_fnc->getStart()) 74 | { 75 | return -1; 76 | } 77 | else 78 | { 79 | return 1; 80 | } 81 | } 82 | 83 | void idaapi demo_place_t::adjust(void* ud) 84 | { 85 | // No idea if some handling is needed here. 86 | // It seems to work OK just like this. 87 | // The following is not working: 88 | // _yx = _fnc->adjust_yx(_yx); 89 | // Sometimes it generates some extra empty lines. 90 | _yx.x = 0; 91 | } 92 | 93 | bool idaapi demo_place_t::prev(void* ud) 94 | { 95 | auto pyx = _fnc->prev_yx(yx()); 96 | if (yx() <= _fnc->min_yx() || pyx == yx()) 97 | { 98 | return false; 99 | } 100 | _yx = pyx; 101 | return true; 102 | } 103 | 104 | bool idaapi demo_place_t::next(void* ud) 105 | { 106 | auto nyx = _fnc->next_yx(yx()); 107 | if (yx() >= _fnc->max_yx() || nyx == yx()) 108 | { 109 | return false; 110 | } 111 | _yx = nyx; 112 | return true; 113 | } 114 | 115 | bool idaapi demo_place_t::beginning(void* ud) const 116 | { 117 | return yx() == _fnc->min_yx(); 118 | } 119 | 120 | bool idaapi demo_place_t::ending(void* ud) const 121 | { 122 | return yx() == _fnc->max_yx(); 123 | } 124 | 125 | int idaapi demo_place_t::generate( 126 | qstrvec_t* out, 127 | int* out_deflnnum, 128 | color_t* out_pfx_color, 129 | bgcolor_t* out_bgcolor, 130 | void* ud, 131 | int maxsize) const 132 | { 133 | if (maxsize <= 0) 134 | { 135 | return 0; 136 | } 137 | if (x() != 0) 138 | { 139 | return 0; 140 | } 141 | 142 | *out_deflnnum = 0; 143 | 144 | std::string str = _fnc->line_yx(yx()); 145 | out->push_back(str.c_str()); 146 | return 1; 147 | } 148 | 149 | // All members must be serialized and deserialized. 150 | // This is apparently used when places are moved around. 151 | // When I didn't serialize _fnc pointer, I lost the info about it when 152 | // place was set to lochist_entry_t. 153 | // However, this is also used when saving/loading IDB, and so if we store and 154 | // than load function pointer, we are in trouble. Instead we serialize functions 155 | // as their addresses and use decompiler to get an actual function pointer. 156 | void idaapi demo_place_t::serialize(bytevec_t* out) const 157 | { 158 | place_t__serialize(this, out); 159 | out->pack_ea(_fnc->getStart()); 160 | out->pack_ea(y()); 161 | out->pack_ea(x()); 162 | } 163 | 164 | bool idaapi demo_place_t::deserialize( 165 | const uchar** pptr, 166 | const uchar* end) 167 | { 168 | if (!place_t__deserialize(this, pptr, end) || *pptr >= end) 169 | { 170 | return false; 171 | } 172 | auto fa = unpack_ea(pptr, end); 173 | _fnc = Decompiler::decompile(fa); 174 | auto y = unpack_ea(pptr, end); 175 | auto x = unpack_ea(pptr, end); 176 | _yx = YX(y, x); 177 | return true; 178 | } 179 | 180 | int idaapi demo_place_t::id() const 181 | { 182 | return demo_place_t::ID; 183 | } 184 | 185 | const char* idaapi demo_place_t::name() const 186 | { 187 | return demo_place_t::_name; 188 | } 189 | 190 | ea_t idaapi demo_place_t::toea() const 191 | { 192 | return _fnc->yx_2_ea(yx()); 193 | } 194 | 195 | bool idaapi demo_place_t::rebase(const segm_move_infos_t&) 196 | { 197 | // nothing 198 | return false; 199 | } 200 | 201 | place_t* idaapi demo_place_t::enter(uint32*) const 202 | { 203 | // nothing 204 | return nullptr; 205 | } 206 | 207 | void idaapi demo_place_t::leave(uint32) const 208 | { 209 | // nothing 210 | } 211 | 212 | int demo_place_t::ID = -1; 213 | 214 | demo_place_t::demo_place_t(Function* fnc, YX yx) 215 | : _fnc(fnc) 216 | , _yx(yx) 217 | { 218 | lnnum = 0; 219 | } 220 | 221 | void demo_place_t::registerPlace(const plugin_t& PLUGIN) 222 | { 223 | demo_place_t::ID = register_place_class( 224 | &_template, 225 | PCF_EA_CAPABLE | PCF_MAKEPLACE_ALLOCATES, 226 | &PLUGIN 227 | ); 228 | 229 | /// Register a converter, that will be used for the following reasons: 230 | /// - determine what view can be synchronized with what other view 231 | /// - when views are synchronized, convert the location from one view, 232 | /// into an appropriate location in the other view 233 | /// - if one of p1 or p2 is "idaplace_t", and the other is PCF_EA_CAPABLE, 234 | /// then the converter will also be called when the user wants to jump to 235 | /// an address (e.g., by pressing "g"). In that case, from's place_t's lnnum 236 | /// will be set to -1 (i.e., can be used to descriminate between proper 237 | /// synchronizations, and jump to's if needed.) 238 | /// 239 | /// Note: the converter can be used to convert in both directions, and can be 240 | /// called with its 'from' being of the class of 'p1', or 'p2'. 241 | /// If you want your converter to work in only one direction (e.g., from 242 | /// 'my_dictionary_place_t' -> 'my_definition_place_t'), you can have it 243 | /// return false when it is called with a lochist_entry_t's whose place is 244 | /// of type 'my_definition_place_t'. 245 | /// 246 | /// Note: Whenever one of the 'p1' or 'p2' places is unregistered, 247 | /// corresponding converters will be automatically unregistered as well. 248 | register_loc_converter( 249 | _template.name(), 250 | _idaplace.name(), 251 | place_converter 252 | ); 253 | } 254 | 255 | YX demo_place_t::yx() const 256 | { 257 | return _yx; 258 | } 259 | 260 | std::size_t demo_place_t::y() const 261 | { 262 | return yx().y; 263 | } 264 | 265 | std::size_t demo_place_t::x() const 266 | { 267 | return yx().x; 268 | } 269 | 270 | const Token* demo_place_t::token() const 271 | { 272 | return fnc()->getToken(yx()); 273 | } 274 | 275 | Function* demo_place_t::fnc() const 276 | { 277 | return _fnc; 278 | } 279 | 280 | std::string demo_place_t::toString() const 281 | { 282 | std::stringstream ss; 283 | ss << *this; 284 | return ss.str(); 285 | } 286 | 287 | std::ostream& operator<<(std::ostream& os, const demo_place_t& p) 288 | { 289 | os << *p.fnc() << p.yx(); 290 | return os; 291 | } 292 | 293 | lecvt_code_t idaapi place_converter( 294 | lochist_entry_t* dst, 295 | const lochist_entry_t& src, 296 | TWidget* view) 297 | { 298 | // idaplace_t -> demo_place_t 299 | if (src.place()->name() == std::string(_idaplace.name())) 300 | { 301 | auto idaEa = src.place()->toea(); 302 | 303 | auto* cur = dynamic_cast(get_custom_viewer_place( 304 | view, 305 | false, // mouse 306 | nullptr, // x 307 | nullptr // y 308 | )); 309 | if (cur == nullptr) 310 | { 311 | return LECVT_ERROR; 312 | } 313 | 314 | if (cur->fnc()->ea_inside(idaEa)) 315 | { 316 | demo_place_t p(cur->fnc(), cur->fnc()->ea_2_yx(idaEa)); 317 | dst->set_place(p); 318 | // Set both x and y, see renderer_info_t comment in demo.cpp. 319 | dst->renderer_info().pos.cy = p.y(); 320 | dst->renderer_info().pos.cx = p.x(); 321 | 322 | demo_msg("place_converter(): idaplace_t (%a) -> demo_place_t (%s)" 323 | " inside currently displayed function\n", 324 | idaEa, 325 | p.toString().c_str() 326 | ); 327 | } 328 | else if (Function* fnc = Decompiler::decompile(idaEa)) 329 | { 330 | demo_place_t cur(fnc, fnc->ea_2_yx(idaEa)); 331 | dst->set_place(cur); 332 | // Set both x and y, see renderer_info_t comment in demo.cpp. 333 | dst->renderer_info().pos.cy = cur.y(); 334 | dst->renderer_info().pos.cx = cur.x(); 335 | 336 | demo_msg("place_converter(): idaplace_t (%a) -> demo_place_t (%s)" 337 | " inside newly displayed function\n", 338 | idaEa, 339 | cur.toString().c_str() 340 | ); 341 | } 342 | else 343 | { 344 | demo_msg("place_converter(): idaplace_t (%a) -> demo_place_t (-)" 345 | " outside decompilable function\n", 346 | idaEa 347 | ); 348 | return LECVT_CANCELED; 349 | } 350 | 351 | return LECVT_OK; 352 | } 353 | // demo_place_t -> idaplace_t 354 | else if (src.place()->name() == std::string(_template.name())) 355 | { 356 | auto* demoPlc = static_cast(src.place()); 357 | idaplace_t p(demoPlc->toea(), 0); 358 | dst->set_place(p); 359 | 360 | demo_msg("place_converter(): demo_place_t (%a) -> idaplace_t (%a)\n", 361 | demoPlc->toea(), 362 | p.toea() 363 | ); 364 | 365 | return LECVT_OK; 366 | } 367 | // should not happen 368 | else 369 | { 370 | demo_msg("place_converter(): should not happen\n"); 371 | return LECVT_CANCELED; 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /place.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HEXRAYS_DEMO_PLACE_H 3 | #define HEXRAYS_DEMO_PLACE_H 4 | 5 | #include 6 | 7 | #include "context.h" 8 | #include "yx.h" 9 | #include "function.h" 10 | 11 | /** 12 | * Denotes a displayed line. 13 | * 14 | * An object may be displayed on one or more lines. All lines of an object are 15 | * generated at once and kept in a linearray_t class. 16 | */ 17 | class demo_place_t : public place_t 18 | { 19 | // Inherited from place_t. 20 | // 21 | public: 22 | /// Generate a short description of the location. 23 | /// This description is used on the status bar. 24 | /// \param out_buf the output buffer 25 | /// \param ud pointer to user-defined context data. 26 | /// Is supplied by ::linearray_t 27 | virtual void idaapi print(qstring* out_buf, void* ud) const override; 28 | 29 | /// Map the location to a number. 30 | /// This mapping is used to draw the vertical scrollbar. 31 | /// \param ud pointer to user-defined context data. 32 | /// Is supplied by ::linearray_t 33 | virtual uval_t idaapi touval(void* ud) const override; 34 | 35 | /// Clone the location. 36 | /// \return a pointer to a copy of the current location in dynamic 37 | /// memory 38 | virtual place_t* idaapi clone(void) const override; 39 | 40 | /// Copy the specified location object to the current object 41 | virtual void idaapi copyfrom(const place_t* from) override; 42 | 43 | /// Map a number to a location. 44 | /// When the user clicks on the scrollbar and drags it, we need to 45 | /// determine the location corresponding to the new scrollbar position. 46 | /// This function is used to determine it. It builds a location object 47 | /// for the specified 'x' and returns a pointer to it. 48 | /// \param ud pointer to user-defined context data. 49 | /// Is supplied by ::linearray_t 50 | /// \param x number to map 51 | /// \param lnnum line number to initialize 'lnnum' 52 | /// \return a static object, no need to destroy it. 53 | virtual place_t* idaapi makeplace( 54 | void* ud, 55 | uval_t y, 56 | int lnnum) const override; 57 | 58 | /// Deprecated. Please consider compare2(const place_t *, void *) instead. 59 | virtual int idaapi compare(const place_t* t2) const override; 60 | 61 | /// Compare two locations except line numbers (lnnum). 62 | /// This function is used to organize loops. 63 | /// For example, if the user has selected an range, its boundaries are remembered 64 | /// as location objects. Any operation within the selection will have the following 65 | /// look: for ( loc=starting_location; loc < ending_location; loc.next() ) 66 | /// In this loop, the comparison function is used. 67 | /// \param t2 the place to compare this one to. 68 | /// \param ud pointer to user-defined context data. 69 | /// \retval -1 if the current location is less than 't2' 70 | /// \retval 0 if the current location is equal to than 't2' 71 | /// \retval 1 if the current location is greater than 't2' 72 | virtual int idaapi compare2(const place_t* t2, void* ud) const override; 73 | 74 | /// Adjust the current location to point to a displayable object. 75 | /// This function validates the location and makes sure that it points 76 | /// to an existing object. For example, if the location points to the 77 | /// middle of an instruction, it will be adjusted to point to the 78 | /// beginning of the instruction. 79 | /// \param ud pointer to user-defined context data. 80 | /// Is supplied by ::linearray_t 81 | virtual void idaapi adjust(void* ud) override; 82 | 83 | /// Move to the previous displayable location. 84 | /// \param ud pointer to user-defined context data. 85 | /// Is supplied by ::linearray_t 86 | /// \return success 87 | virtual bool idaapi prev(void* ud) override; 88 | 89 | /// Move to the next displayable location. 90 | /// \param ud pointer to user-defined context data. 91 | /// Is supplied by ::linearray_t 92 | /// \return success 93 | virtual bool idaapi next(void* ud) override; 94 | 95 | /// Are we at the first displayable object?. 96 | /// \param ud pointer to user-defined context data. 97 | /// Is supplied by ::linearray_t 98 | /// \return true if the current location points to the first 99 | /// displayable object 100 | virtual bool idaapi beginning(void* ud) const override; 101 | 102 | /// Are we at the last displayable object?. 103 | /// \param ud pointer to user-defined context data. 104 | /// Is supplied by ::linearray_t 105 | /// \return true if the current location points to the last 106 | /// displayable object 107 | virtual bool idaapi ending(void* ud) const override; 108 | 109 | /// Generate text lines for the current location. 110 | /// \param out storage for the lines 111 | /// \param out_deflnnum pointer to the cell that will contain the num 112 | /// of the most 'interesting' generated line 113 | /// \param out_pfx_color pointer to the cell that will contain the 114 | /// line prefix color 115 | /// \param out_bgcolor pointer to the cell that will contain the 116 | /// background color 117 | /// \param ud pointer to user-defined context data. 118 | /// Is supplied by linearray_t 119 | /// \param maxsize the maximum number of lines to generate 120 | /// \return number of generated lines 121 | virtual int idaapi generate( 122 | qstrvec_t* out, 123 | int* out_deflnnum, 124 | color_t* out_pfx_color, 125 | bgcolor_t* out_bgcolor, 126 | void* ud, 127 | int maxsize) const override; 128 | 129 | /// Serialize this instance. 130 | /// It is fundamental that all instances of a particular subclass 131 | /// of of place_t occupy the same number of bytes when serialized. 132 | /// \param out buffer to serialize into 133 | virtual void idaapi serialize(bytevec_t* out) const override; 134 | 135 | /// De-serialize into this instance. 136 | /// 'pptr' should be incremented by as many bytes as 137 | /// de-serialization consumed. 138 | /// \param pptr pointer to a serialized representation of a place_t 139 | /// of this type. 140 | /// \param end pointer to end of buffer. 141 | /// \return whether de-serialization was successful 142 | virtual bool idaapi deserialize( 143 | const uchar** pptr, 144 | const uchar* end) override; 145 | 146 | /// Get the place's ID (i.e., the value returned by 147 | /// register_place_class()) 148 | /// \return the id 149 | virtual int idaapi id() const override; 150 | 151 | /// Get this place type name. 152 | /// All instances of a given class must return the same string. 153 | /// \return the place type name. Please try and pick something that is 154 | /// not too generic, as it might clash w/ other plugins. A good 155 | /// practice is to prefix the class name with the name 156 | /// of your plugin. E.g., "myplugin:srcplace_t". 157 | virtual const char* idaapi name() const override; 158 | 159 | /// Map the location to an ea_t. 160 | /// \return the corresponding ea_t, or BADADDR; 161 | virtual ea_t idaapi toea() const override; 162 | 163 | /// Rebase the place instance 164 | /// \param infos the segments that were moved 165 | /// \return true if place was rebased, false otherwise 166 | virtual bool idaapi rebase(const segm_move_infos_t&) override; 167 | 168 | /// Visit this place, possibly 'unhiding' a section of text. 169 | /// If entering that place required some expanding, a place_t 170 | /// should be returned that represents that section, plus some 171 | /// flags for later use by 'leave()'. 172 | /// \param out_flags flags to be used together with the place_t that is 173 | /// returned, in order to restore the section to its 174 | /// original state when leave() is called. 175 | /// \return a place_t corresponding to the beginning of the section 176 | /// of text that had to be expanded. That place_t's leave() will 177 | /// be called with the flags contained in 'out_flags' when the 178 | /// user navigates away from it. 179 | virtual place_t* idaapi enter(uint32*) const override; 180 | 181 | /// Leave this place, possibly 'hiding' a section of text that was 182 | /// previously expanded (at enter()-time.) 183 | virtual void idaapi leave(uint32) const override; 184 | 185 | public: 186 | static int ID; 187 | 188 | demo_place_t(Function* fnc, YX yx); 189 | static void registerPlace(const plugin_t& PLUGIN); 190 | 191 | YX yx() const; 192 | std::size_t y() const; 193 | std::size_t x() const; 194 | const Token* token() const; 195 | Function* fnc() const; 196 | 197 | std::string toString() const; 198 | friend std::ostream& operator<<( 199 | std::ostream& os, 200 | const demo_place_t& p 201 | ); 202 | 203 | private: 204 | inline static const char* _name = "demo_place_t"; 205 | 206 | Function* _fnc = nullptr; 207 | YX _yx; 208 | }; 209 | 210 | /// Converts from an entry with a given place type, to another entry, 211 | /// with another place type, to be used with the view 'view'. Typically 212 | /// used when views are synchronized. 213 | /// The 'renderer_info_t' part of 'dst' will be pre-filled with 214 | /// the current renderer_info_t of 'view', while the 'place_t' instance 215 | /// will always be NULL. 216 | /// 217 | /// lochist_entry_cvt_t 218 | /// 219 | lecvt_code_t idaapi place_converter( 220 | lochist_entry_t* dst, 221 | const lochist_entry_t& src, 222 | TWidget* view 223 | ); 224 | 225 | #endif 226 | -------------------------------------------------------------------------------- /token.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "token.h" 5 | 6 | std::map TokenColors = 7 | { 8 | {Token::Kind::NEW_LINE, SCOLOR_DEFAULT}, 9 | {Token::Kind::WHITE_SPACE, SCOLOR_DEFAULT}, 10 | {Token::Kind::PUNCTUATION, SCOLOR_KEYWORD}, 11 | {Token::Kind::OPERATOR, SCOLOR_KEYWORD}, 12 | {Token::Kind::ID_VAR, SCOLOR_DREF}, 13 | {Token::Kind::ID_MEM, SCOLOR_DREF}, 14 | {Token::Kind::ID_LAB, SCOLOR_DREF}, 15 | {Token::Kind::ID_FNC, SCOLOR_DEFAULT}, 16 | {Token::Kind::ID_ARG, SCOLOR_DREF}, 17 | {Token::Kind::KEYWORD, SCOLOR_MACRO}, 18 | {Token::Kind::TYPE, SCOLOR_MACRO}, 19 | {Token::Kind::PREPROCESSOR, SCOLOR_AUTOCMT}, 20 | {Token::Kind::INCLUDE, SCOLOR_NUMBER}, 21 | {Token::Kind::LITERAL_BOOL, SCOLOR_NUMBER}, 22 | {Token::Kind::LITERAL_INT, SCOLOR_NUMBER}, 23 | {Token::Kind::LITERAL_FP, SCOLOR_NUMBER}, 24 | {Token::Kind::LITERAL_STR, SCOLOR_NUMBER}, 25 | {Token::Kind::LITERAL_SYM, SCOLOR_NUMBER}, 26 | {Token::Kind::LITERAL_PTR, SCOLOR_NUMBER}, 27 | {Token::Kind::COMMENT, SCOLOR_AUTOCMT}, 28 | }; 29 | 30 | std::map TokenKindStrings = 31 | { 32 | {Token::Kind::NEW_LINE, "NEW_LINE"}, 33 | {Token::Kind::WHITE_SPACE, "WHITE_SPACE"}, 34 | {Token::Kind::PUNCTUATION, "PUNCTUATION"}, 35 | {Token::Kind::OPERATOR, "OPERATOR"}, 36 | {Token::Kind::ID_VAR, "ID_VAR"}, 37 | {Token::Kind::ID_MEM, "ID_MEM"}, 38 | {Token::Kind::ID_LAB, "ID_LAB"}, 39 | {Token::Kind::ID_FNC, "ID_FNC"}, 40 | {Token::Kind::ID_ARG, "ID_ARG"}, 41 | {Token::Kind::KEYWORD, "KEYWORD"}, 42 | {Token::Kind::TYPE, "TYPE"}, 43 | {Token::Kind::PREPROCESSOR, "PREPROCESSOR"}, 44 | {Token::Kind::INCLUDE, "INCLUDE"}, 45 | {Token::Kind::LITERAL_BOOL, "LITERAL_BOOL"}, 46 | {Token::Kind::LITERAL_INT, "LITERAL_INT"}, 47 | {Token::Kind::LITERAL_FP, "LITERAL_FP"}, 48 | {Token::Kind::LITERAL_STR, "LITERAL_STR"}, 49 | {Token::Kind::LITERAL_SYM, "LITERAL_SYM"}, 50 | {Token::Kind::LITERAL_PTR, "LITERAL_PTR"}, 51 | {Token::Kind::COMMENT, "COMMENT"}, 52 | }; 53 | 54 | const std::string& Token::getKindString() const 55 | { 56 | return TokenKindStrings[kind]; 57 | } 58 | 59 | const std::string& Token::getColorTag() const 60 | { 61 | return TokenColors[kind]; 62 | } 63 | -------------------------------------------------------------------------------- /token.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HEXRAYS_DEMO_TOKEN_H 3 | #define HEXRAYS_DEMO_TOKEN_H 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | /** 11 | * One element (lexical unit) in the decompiled source code. 12 | * 13 | * Closely related to: 14 | * https://github.com/avast/retdec/wiki/Decompiler-outputs#json-output-file-format 15 | */ 16 | struct Token 17 | { 18 | enum class Kind 19 | { 20 | NEW_LINE = 0, 21 | WHITE_SPACE, 22 | PUNCTUATION, 23 | OPERATOR, 24 | ID_VAR, 25 | ID_MEM, 26 | ID_LAB, 27 | ID_FNC, 28 | ID_ARG, 29 | KEYWORD, 30 | TYPE, 31 | PREPROCESSOR, 32 | INCLUDE, 33 | LITERAL_BOOL, 34 | LITERAL_INT, 35 | LITERAL_FP, 36 | LITERAL_STR, 37 | LITERAL_SYM, 38 | LITERAL_PTR, 39 | COMMENT, 40 | }; 41 | 42 | Kind kind; 43 | ea_t ea; 44 | std::string value; 45 | 46 | const std::string& getKindString() const; 47 | const std::string& getColorTag() const; 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /ui.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "context.h" 3 | #include "decompiler.h" 4 | #include "place.h" 5 | #include "ui.h" 6 | 7 | int idaapi function_ctx_ah_t::activate(action_activation_ctx_t*) 8 | { 9 | info("function context action"); 10 | return false; 11 | } 12 | 13 | action_state_t idaapi function_ctx_ah_t::update(action_update_ctx_t*) 14 | { 15 | return AST_ENABLE_ALWAYS; 16 | } 17 | 18 | int idaapi variable_ctx_ah_t::activate(action_activation_ctx_t*) 19 | { 20 | info("variable context action"); 21 | return false; 22 | } 23 | 24 | action_state_t idaapi variable_ctx_ah_t::update(action_update_ctx_t*) 25 | { 26 | return AST_ENABLE_ALWAYS; 27 | } 28 | 29 | copy2asm_ah_t::copy2asm_ah_t(Context& c) 30 | : ctx(c) 31 | { 32 | 33 | } 34 | 35 | int idaapi copy2asm_ah_t::activate(action_activation_ctx_t*) 36 | { 37 | static const char* text = "Copying pseudocode to disassembly" 38 | " will destroy existing comments.\n" 39 | "Do you want to continue?"; 40 | if (ask_yn(ASKBTN_NO, text) == ASKBTN_YES) 41 | { 42 | for (auto& p : ctx.fnc->toLines()) 43 | { 44 | ea_t addr = p.second; 45 | auto& line = p.first; 46 | 47 | delete_extra_cmts(addr, E_PREV); 48 | bool anteriorCmt = true; 49 | add_extra_cmt(addr, anteriorCmt, "%s", line.c_str()); 50 | } 51 | 52 | // Focus to IDA view. 53 | auto* place = dynamic_cast(get_custom_viewer_place( 54 | ctx.custViewer, 55 | false, // mouse 56 | nullptr, // x 57 | nullptr // y 58 | )); 59 | if (place != nullptr) 60 | { 61 | jumpto(place->toea(), 0, UIJMP_ACTIVATE | UIJMP_IDAVIEW); 62 | } 63 | } 64 | return false; 65 | } 66 | 67 | action_state_t idaapi copy2asm_ah_t::update(action_update_ctx_t*) 68 | { 69 | return AST_ENABLE_ALWAYS; 70 | } 71 | 72 | /** 73 | * User interface hook. 74 | */ 75 | ssize_t idaapi Context::on_event(ssize_t code, va_list va) 76 | { 77 | switch (code) 78 | { 79 | // IDA is populating the context menu (right-click menu) for a widget. 80 | // We can attach action to popup - i.e. create menu on the fly. 81 | case ui_populating_widget_popup: 82 | { 83 | // Continue only if event was triggered in our widget. 84 | TWidget* view = va_arg(va, TWidget*); 85 | TPopupMenu* popup = va_arg(va, TPopupMenu*); 86 | if (view != custViewer && view != codeViewer) 87 | { 88 | return false; 89 | } 90 | 91 | auto* place = dynamic_cast(get_custom_viewer_place( 92 | view, 93 | false, // mouse 94 | nullptr, // x 95 | nullptr // y 96 | )); 97 | if (place == nullptr) 98 | { 99 | return false; 100 | } 101 | 102 | auto* token = place->token(); 103 | if (token == nullptr) 104 | { 105 | return false; 106 | } 107 | 108 | demo_msg("on_event(ui_populating_widget_popup):" 109 | " token=|%s| @ %s\n", 110 | token->value.c_str(), 111 | place->toString().c_str() 112 | ); 113 | 114 | if (token->kind == Token::Kind::ID_FNC) 115 | { 116 | attach_action_to_popup( 117 | view, 118 | popup, 119 | function_ctx_ah_t::actionName 120 | ); 121 | } 122 | else if (token->kind == Token::Kind::ID_VAR) 123 | { 124 | attach_action_to_popup( 125 | view, 126 | popup, 127 | variable_ctx_ah_t::actionName 128 | ); 129 | } 130 | 131 | attach_action_to_popup( 132 | view, 133 | popup, 134 | copy2asm_ah_t::actionName 135 | ); 136 | 137 | break; 138 | } 139 | 140 | 141 | case ui_get_lines_rendering_info: 142 | { 143 | auto* demoSyncGroup = get_synced_group(custViewer); 144 | if (demoSyncGroup == nullptr) 145 | { 146 | return false; 147 | } 148 | 149 | auto* demoPlace = dynamic_cast(get_custom_viewer_place( 150 | custViewer, 151 | false, // mouse 152 | nullptr, // x 153 | nullptr // y 154 | )); 155 | if (demoPlace == nullptr) 156 | { 157 | return false; 158 | } 159 | auto eas = demoPlace->fnc()->yx_2_eas(demoPlace->yx()); 160 | 161 | lines_rendering_output_t* out = va_arg(va, lines_rendering_output_t*); 162 | TWidget* view = va_arg(va, TWidget*); 163 | lines_rendering_input_t* info = va_arg(va, lines_rendering_input_t*); 164 | 165 | if (view == nullptr || info->sync_group != demoSyncGroup) 166 | { 167 | return false; 168 | } 169 | 170 | for (auto& sl : info->sections_lines) 171 | for (auto& l : sl) 172 | { 173 | if (eas.count(l->at->toea())) 174 | { 175 | out->entries.push_back(new line_rendering_output_entry_t( 176 | l, 177 | LROEF_FULL_LINE, 178 | 0xff000000 + syncColor 179 | )); 180 | } 181 | } 182 | 183 | break; 184 | } 185 | 186 | // TWidget is being closed. 187 | case ui_widget_invisible: 188 | { 189 | TWidget* view = va_arg(va, TWidget*); 190 | if (view != custViewer && view != codeViewer) 191 | { 192 | return false; 193 | } 194 | 195 | demo_msg("on_event(ui_widget_invisible)\n"); 196 | 197 | unhook_event_listener(HT_UI, this); 198 | custViewer = nullptr; 199 | codeViewer = nullptr; 200 | break; 201 | } 202 | } 203 | 204 | return false; 205 | } 206 | 207 | /** 208 | * Called whenever the user moves the cursor around (mouse, keyboard). 209 | * Fine-tune 'loc->place()' according to the X position. 210 | * 211 | * Without this, demo_place_t's X position would not change when cursor moves 212 | * around. 213 | * Changing the position triggers some actions. E.g. demo_place_t::print(). 214 | * 215 | * custom_viewer_adjust_place_t 216 | */ 217 | void idaapi cv_adjust_place(TWidget* v, lochist_entry_t* loc, void* ud) 218 | { 219 | auto* plc = static_cast(loc->place()); 220 | auto* fnc = plc->fnc(); 221 | 222 | demo_place_t nplc( 223 | fnc, 224 | fnc->adjust_yx(YX( 225 | plc->y(), 226 | loc->renderer_info().pos.cx 227 | ))); 228 | 229 | if (plc->compare(&nplc) != 0) // not equal 230 | { 231 | demo_msg("cv_adjust_place() @ %s\n", fnc->getName().c_str()); 232 | loc->set_place(nplc); 233 | } 234 | } 235 | 236 | bool idaapi cv_keyboard(TWidget *cv, int vk_key, int shift, void *ud) 237 | { 238 | // A 239 | if (vk_key == 65 && shift == 0) 240 | { 241 | auto* place = dynamic_cast(get_custom_viewer_place( 242 | cv, 243 | false, // mouse 244 | nullptr, // x 245 | nullptr // y 246 | )); 247 | if (place == nullptr) 248 | { 249 | return false; 250 | } 251 | 252 | demo_msg("cv_keyboard(): jumping to IDA view @ %a\n", place->toea()); 253 | 254 | // Jump to IDA view. 255 | jumpto(place->toea(), 0, UIJMP_ACTIVATE | UIJMP_IDAVIEW); 256 | } 257 | 258 | return true; 259 | } 260 | 261 | bool idaapi cv_double(TWidget* cv, int shift, void* ud) 262 | { 263 | auto* place = dynamic_cast(get_custom_viewer_place( 264 | cv, 265 | false, // mouse 266 | nullptr, // x 267 | nullptr // y 268 | )); 269 | if (place == nullptr) 270 | { 271 | return false; 272 | } 273 | 274 | auto* token = place->token(); 275 | if (token == nullptr || token->kind != Token::Kind::ID_FNC) 276 | { 277 | return false; 278 | } 279 | auto fncName = token->value; 280 | 281 | demo_msg("cv_double(): token=|%s| @ %s\n", 282 | fncName.c_str(), 283 | place->toString().c_str() 284 | ); 285 | 286 | func_t* fnc = nullptr; 287 | for (unsigned i = 0; i < get_func_qty(); ++i) 288 | { 289 | func_t* f = getn_func(i); 290 | qstring qFncName; 291 | get_func_name(&qFncName, f->start_ea); 292 | if (qFncName.c_str() == fncName) 293 | { 294 | fnc = f; 295 | break; 296 | } 297 | } 298 | 299 | if (fnc == nullptr) 300 | { 301 | demo_msg("cv_double(): function \"%s\" not found in IDA functions\n", 302 | fncName.c_str() 303 | ); 304 | return false; 305 | } 306 | 307 | 308 | demo_msg("cv_double(): jumping to %a\n", 309 | fnc->start_ea 310 | ); 311 | jumpto(fnc->start_ea, -1, UIJMP_ACTIVATE); 312 | 313 | return true; 314 | } 315 | 316 | /** 317 | * custom_viewer_location_changed_t 318 | */ 319 | void idaapi cv_location_changed( 320 | TWidget* v, 321 | const lochist_entry_t* was, 322 | const lochist_entry_t* now, 323 | const locchange_md_t& md, 324 | void* ud) 325 | { 326 | Context* ctx = static_cast(ud); 327 | 328 | auto* oldp = dynamic_cast(was->place()); 329 | auto* newp = dynamic_cast(now->place()); 330 | if (oldp->compare(newp) == 0) // equal 331 | { 332 | return; 333 | } 334 | 335 | std::string reason; 336 | switch (md.reason()) 337 | { 338 | case lcr_goto: reason = "lcr_goto"; break; 339 | case lcr_user_switch: reason = "lcr_user_switch"; break; 340 | case lcr_auto_switch: reason = "lcr_auto_switch"; break; 341 | case lcr_jump: reason = "lcr_jump"; break; 342 | case lcr_navigate: reason = "lcr_navigate"; break; 343 | case lcr_scroll: reason = "lcr_scroll"; break; 344 | case lcr_internal: reason = "lcr_internal"; break; 345 | default: reason = "lcr_unknown"; break; 346 | } 347 | demo_msg("cv_location_changed(): reason = %s, synced = %d," 348 | " %s ==> %s\n", 349 | reason.c_str(), 350 | md.is_sync(), 351 | oldp->toString().c_str(), 352 | newp->toString().c_str() 353 | ); 354 | 355 | if (oldp->fnc() != newp->fnc()) 356 | { 357 | demo_place_t min(newp->fnc(), newp->fnc()->min_yx()); 358 | demo_place_t max(newp->fnc(), newp->fnc()->max_yx()); 359 | set_custom_viewer_range(ctx->custViewer, &min, &max); 360 | ctx->fnc = newp->fnc(); 361 | } 362 | } 363 | 364 | /** 365 | * custom_viewer_get_place_xcoord_t 366 | */ 367 | int idaapi cv_get_place_xcoord( 368 | TWidget* v, 369 | const place_t* pline, 370 | const place_t* pitem, 371 | void* ud) 372 | { 373 | auto* mpline = static_cast(pline); 374 | auto* mpitem = static_cast(pitem); 375 | 376 | if (mpline->y() != mpitem->y()) 377 | { 378 | return -1; // not included 379 | } 380 | // i.e. mpline->y() == mpitem->y() 381 | else if (mpitem->x() == 0) 382 | { 383 | return -2; // points to entire line 384 | } 385 | else 386 | { 387 | return mpitem->x(); // included at coordinate 388 | } 389 | } 390 | 391 | /** 392 | * custom_viewer_can_navigate_t 393 | * 394 | * I can't seem to trigger this. 395 | */ 396 | int idaapi cv_can_navigate( 397 | TWidget *v, 398 | const lochist_entry_t *now, 399 | const locchange_md_t &md, 400 | void *ud) 401 | { 402 | demo_msg("cv_can_navigate()\n"); 403 | return 0; 404 | } -------------------------------------------------------------------------------- /ui.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HEXRAYS_DEMO_UI_H 3 | #define HEXRAYS_DEMO_UI_H 4 | 5 | class Context; 6 | 7 | struct function_ctx_ah_t : public action_handler_t 8 | { 9 | inline static const char* actionName = "demo:ActionFunctionCtx"; 10 | inline static const char* actionLabel = "Function context"; 11 | inline static const char* actionHotkey = "F"; 12 | 13 | virtual int idaapi activate(action_activation_ctx_t*) override; 14 | virtual action_state_t idaapi update(action_update_ctx_t*) override; 15 | }; 16 | 17 | struct variable_ctx_ah_t : public action_handler_t 18 | { 19 | inline static const char* actionName = "demo:ActionVariableCtx"; 20 | inline static const char* actionLabel = "Variable context"; 21 | inline static const char* actionHotkey = "V"; 22 | 23 | virtual int idaapi activate(action_activation_ctx_t*) override; 24 | virtual action_state_t idaapi update(action_update_ctx_t*) override; 25 | }; 26 | 27 | struct copy2asm_ah_t : public action_handler_t 28 | { 29 | inline static const char* actionName = "demo:ActionCopy2Asm"; 30 | inline static const char* actionLabel = "Copy to assembly"; 31 | inline static const char* actionHotkey = "C"; 32 | 33 | Context& ctx; 34 | copy2asm_ah_t(Context& c); 35 | 36 | virtual int idaapi activate(action_activation_ctx_t*) override; 37 | virtual action_state_t idaapi update(action_update_ctx_t*) override; 38 | }; 39 | 40 | bool idaapi cv_keyboard(TWidget *cv, int vk_key, int shift, void *ud); 41 | bool idaapi cv_double(TWidget* cv, int shift, void* ud); 42 | void idaapi cv_adjust_place(TWidget* v, lochist_entry_t* loc, void* ud); 43 | int idaapi cv_get_place_xcoord( 44 | TWidget* v, 45 | const place_t* pline, 46 | const place_t* pitem, 47 | void* ud 48 | ); 49 | void idaapi cv_location_changed( 50 | TWidget *v, 51 | const lochist_entry_t* was, 52 | const lochist_entry_t* now, 53 | const locchange_md_t& md, 54 | void* ud 55 | ); 56 | int idaapi cv_can_navigate( 57 | TWidget *v, 58 | const lochist_entry_t *now, 59 | const locchange_md_t &md, 60 | void *ud 61 | ); 62 | 63 | static const custom_viewer_handlers_t ui_handlers( 64 | cv_keyboard, // keyboard 65 | nullptr, // popup 66 | nullptr, // mouse_moved 67 | nullptr, // click 68 | cv_double, // dblclick 69 | nullptr, // current position change 70 | nullptr, // close 71 | nullptr, // help 72 | cv_adjust_place, // adjust_place 73 | cv_get_place_xcoord, // get_place_xcoord 74 | cv_location_changed, // location_changed 75 | cv_can_navigate // can_navigate 76 | ); 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /yx.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "yx.h" 5 | 6 | const YX YX::starting_yx = YX(YX::starting_y, YX::starting_x); 7 | 8 | YX::YX(std::size_t _y, std::size_t _x) 9 | : y(_y) 10 | , x(_x) 11 | { 12 | 13 | } 14 | 15 | bool YX::operator<(const YX& rhs) const 16 | { 17 | std::pair _this(y, x); 18 | std::pair other(rhs.y, rhs.x); 19 | return _this < other; 20 | } 21 | 22 | bool YX::operator<=(const YX& rhs) const 23 | { 24 | std::pair _this(y, x); 25 | std::pair other(rhs.y, rhs.x); 26 | return _this <= other; 27 | } 28 | 29 | bool YX::operator>(const YX& rhs) const 30 | { 31 | std::pair _this(y, x); 32 | std::pair other(rhs.y, rhs.x); 33 | return _this > other; 34 | } 35 | 36 | bool YX::operator>=(const YX& rhs) const 37 | { 38 | std::pair _this(y, x); 39 | std::pair other(rhs.y, rhs.x); 40 | return _this >= other; 41 | } 42 | 43 | bool YX::operator==(const YX& rhs) const 44 | { 45 | std::pair _this(y, x); 46 | std::pair other(rhs.y, rhs.x); 47 | return _this == other; 48 | } 49 | 50 | std::string YX::toString() const 51 | { 52 | std::stringstream ss; 53 | ss << *this; 54 | return ss.str(); 55 | } 56 | 57 | std::ostream& operator<<(std::ostream& os, const YX& yx) 58 | { 59 | os << "[y=" << std::dec << yx.y << ",x=" << std::dec << yx.x << "]"; 60 | return os; 61 | } 62 | -------------------------------------------------------------------------------- /yx.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HEXRAYS_DEMO_YX_H 3 | #define HEXRAYS_DEMO_YX_H 4 | 5 | #include 6 | #include 7 | 8 | /** 9 | * YX coordinates 10 | * Y = lines 11 | * X = columns 12 | */ 13 | struct YX 14 | { 15 | /// lines 16 | std::size_t y = YX::starting_y; 17 | /// columns 18 | std::size_t x = YX::starting_x; 19 | 20 | inline static const std::size_t starting_y = 1; 21 | inline static const std::size_t starting_x = 0; 22 | static const YX starting_yx; 23 | 24 | YX(std::size_t _y = starting_y, std::size_t _x = starting_x); 25 | 26 | bool operator<(const YX& rhs) const; 27 | bool operator<=(const YX& rhs) const; 28 | bool operator>(const YX& rhs) const; 29 | bool operator>=(const YX& rhs) const; 30 | bool operator==(const YX& rhs) const; 31 | 32 | std::string toString() const; 33 | friend std::ostream& operator<<(std::ostream& os, const YX& yx); 34 | }; 35 | 36 | #endif 37 | --------------------------------------------------------------------------------