├── src ├── zasm-modules │ ├── module.cpp │ ├── module.pe.hpp │ └── module.pe.cpp └── example │ └── main.cpp ├── thirdparty ├── cmake.toml └── CMakeLists.txt ├── include └── zasm │ └── modules │ └── module.hpp ├── cmake.toml ├── LICENSE ├── README.md ├── .clang-format ├── CMakeLists.txt └── cmkr.cmake /src/zasm-modules/module.cpp: -------------------------------------------------------------------------------- 1 | #include "module.pe.hpp" 2 | 3 | #include 4 | 5 | namespace zasm::modules 6 | { 7 | 8 | std::unique_ptr createModule(const ModuleType type, const Program& program, const char* name) 9 | { 10 | if (type == ModuleType::PE) 11 | return std::make_unique(program, name); 12 | 13 | return nullptr; 14 | } 15 | 16 | } // namespace zasm::frontend -------------------------------------------------------------------------------- /thirdparty/cmake.toml: -------------------------------------------------------------------------------- 1 | # Reference: https://build-cpp.github.io/cmkr/cmake-toml 2 | 3 | [options] 4 | LIEF_EXAMPLES = false 5 | LIEF_USE_CCACHE = false 6 | LIEF_ENABLE_JSON = false 7 | LIEF_TESTS = false 8 | ZASM_BUILD_TESTS = false 9 | ZASM_BUILD_BENCHMARKS = false 10 | 11 | [fetch-content.LIEF] 12 | git = "https://github.com/lief-project/LIEF.git" 13 | tag = "ddbf40f" 14 | 15 | [fetch-content.zasm] 16 | git = "https://github.com/zyantific/zasm.git" 17 | tag = "fb8b7357cb674f3c29a17ed2444a6355607bdef4" -------------------------------------------------------------------------------- /src/zasm-modules/module.pe.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace zasm::modules 8 | { 9 | class ModulePE final : public Module 10 | { 11 | const Program& _program; 12 | LIEF::PE::Binary _binary; 13 | 14 | public: 15 | ModulePE(const Program& program, const char* name); 16 | 17 | ModuleType getModuleType() const override 18 | { 19 | return ModuleType::PE; 20 | } 21 | 22 | Error serialize() override; 23 | 24 | Error save(const std::filesystem::path& filePath) override; 25 | }; 26 | 27 | } // namespace zasm::modules -------------------------------------------------------------------------------- /include/zasm/modules/module.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace zasm::modules 8 | { 9 | enum class ModuleType 10 | { 11 | PE, 12 | }; 13 | 14 | class Module 15 | { 16 | public: 17 | virtual ~Module() = default; 18 | 19 | virtual ModuleType getModuleType() const = 0; 20 | 21 | virtual Error serialize() = 0; 22 | 23 | virtual Error save(const std::filesystem::path& filePath) = 0; 24 | }; 25 | 26 | std::unique_ptr createModule(const ModuleType type, const Program& program, const char* name); 27 | 28 | } // namespace zasm::frontend -------------------------------------------------------------------------------- /cmake.toml: -------------------------------------------------------------------------------- 1 | # Reference: https://build-cpp.github.io/cmkr/cmake-toml 2 | [project] 3 | name = "zasm-modules" 4 | languages = ["CXX"] 5 | 6 | [subdir.thirdparty] 7 | 8 | [target.zasm-modules] 9 | alias = "zasm::modules" 10 | type = "static" 11 | sources = ["src/zasm-modules/**.cpp"] 12 | headers = ["src/zasm-modules/*.hpp", "include/zasm/modules/*.hpp"] 13 | include-directories = ["include"] 14 | compile-features = ["cxx_std_17"] 15 | link-libraries = [ 16 | "zasm::common", 17 | "zasm::zasm", 18 | "LIEF::LIEF", 19 | ] 20 | 21 | [target.example] 22 | type = "executable" 23 | sources = ["src/example/**.cpp"] 24 | headers = ["src/example/**.hpp"] 25 | compile-features = ["cxx_std_17"] 26 | link-libraries = [ 27 | "zasm::common", 28 | "zasm::modules", 29 | ] -------------------------------------------------------------------------------- /thirdparty/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is automatically generated from cmake.toml - DO NOT EDIT 2 | # See https://github.com/build-cpp/cmkr for more information 3 | 4 | # Create a configure-time dependency on cmake.toml to improve IDE support 5 | if(CMKR_ROOT_PROJECT) 6 | configure_file(cmake.toml cmake.toml COPYONLY) 7 | endif() 8 | 9 | # Options 10 | option(LIEF_EXAMPLES "" OFF) 11 | option(LIEF_USE_CCACHE "" OFF) 12 | option(LIEF_ENABLE_JSON "" OFF) 13 | option(LIEF_TESTS "" OFF) 14 | option(ZASM_BUILD_TESTS "" OFF) 15 | option(ZASM_BUILD_BENCHMARKS "" OFF) 16 | 17 | include(FetchContent) 18 | 19 | message(STATUS "Fetching LIEF (ddbf40f)...") 20 | FetchContent_Declare(LIEF 21 | GIT_REPOSITORY 22 | "https://github.com/lief-project/LIEF.git" 23 | GIT_TAG 24 | ddbf40f 25 | ) 26 | FetchContent_MakeAvailable(LIEF) 27 | 28 | message(STATUS "Fetching zasm (fb8b7357cb674f3c29a17ed2444a6355607bdef4)...") 29 | FetchContent_Declare(zasm 30 | GIT_REPOSITORY 31 | "https://github.com/zyantific/zasm.git" 32 | GIT_TAG 33 | fb8b7357cb674f3c29a17ed2444a6355607bdef4 34 | ) 35 | FetchContent_MakeAvailable(zasm) 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ζeh Matt 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 | # zasm-modules 2 | Generating binary modules with [zasm](https://github.com/zyantific/zasm) and [LIEF](https://github.com/lief-project/LIEF). 3 | 4 | ## Project 5 | This project is currently more of a demonstration for how zasm could be used to generate binary modules, 6 | zasm provides enough information to have imports/externals/relocations/entrypoints. 7 | 8 | ## LIEF 9 | This project builds upon [LIEF](https://github.com/lief-project/LIEF) to generate the binary module(s), the interface was built to hide this. 10 | LIEF can currently only generate PE files from scratch so it might be replaced in the future, this depends if and when LIEF might add 11 | support for generating more binaries from scratch. LIEF is a wonderful library and we do hope to be able to make more use of it. 12 | 13 | ## Goals 14 | Add more support for modules like COFF and potentially be fully independent from LIEF, this is not set in stone. 15 | 16 | ## Example 17 | See the [example here](https://github.com/ZehMatt/zasm-modules/blob/master/src/example/main.cpp) 18 | 19 | ## Compiling 20 | This library ships with an example project and uses CMake. Following should be able to build the 21 | project: 22 | ``` 23 | cmake . -B build 24 | cmake --build . 25 | ``` 26 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # BasedOnStyle: WebKit 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: AlwaysBreak 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlines: Right 8 | AlignOperands: false 9 | AlignTrailingComments: true 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: None 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterDefinitionReturnType: None 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: false 19 | AlwaysBreakTemplateDeclarations: false 20 | BinPackArguments: true 21 | BinPackParameters: true 22 | BraceWrapping: 23 | AfterCaseLabel: true 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterStruct: true 30 | AfterUnion: true 31 | BeforeCatch: true 32 | BeforeElse: true 33 | IndentBraces: false 34 | SplitEmptyFunction: true 35 | SplitEmptyRecord: true 36 | SplitEmptyNamespace: true 37 | BreakBeforeBinaryOperators: All 38 | BreakBeforeBraces: Custom 39 | BreakBeforeInheritanceComma: false 40 | BreakBeforeTernaryOperators: true 41 | BreakConstructorInitializersBeforeComma: false 42 | BreakConstructorInitializers: BeforeComma 43 | BreakStringLiterals: true 44 | ColumnLimit: 128 45 | CompactNamespaces: false 46 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 47 | ConstructorInitializerIndentWidth: 4 48 | ContinuationIndentWidth: 4 49 | Cpp11BracedListStyle: false 50 | DerivePointerAlignment: false 51 | DisableFormat: false 52 | ExperimentalAutoDetectBinPacking: false 53 | FixNamespaceComments: true 54 | IncludeBlocks: 'Regroup' 55 | IncludeCategories: 56 | - Regex: '^"' 57 | Priority: 1 58 | - Regex: '^<' 59 | Priority: 2 60 | IndentCaseLabels: true 61 | IndentPPDirectives: AfterHash 62 | IndentWidth: 4 63 | IndentWrappedFunctionNames: true 64 | KeepEmptyLinesAtTheStartOfBlocks: false 65 | MacroBlockBegin: '' 66 | MacroBlockEnd: '' 67 | MaxEmptyLinesToKeep: 1 68 | NamespaceIndentation: All 69 | PenaltyBreakAssignment: 1000 70 | PenaltyBreakBeforeFirstCallParameter: 19 71 | PenaltyBreakComment: 300 72 | PenaltyBreakFirstLessLess: 120 73 | PenaltyBreakString: 1000 74 | PenaltyExcessCharacter: 1000000 75 | PenaltyReturnTypeOnItsOwnLine: 1000 76 | PointerAlignment: Left 77 | ReflowComments: true 78 | SortIncludes: true 79 | SortUsingDeclarations: true 80 | SpaceAfterCStyleCast: false 81 | SpaceAfterTemplateKeyword: false 82 | SpaceBeforeAssignmentOperators: true 83 | SpaceBeforeParens: ControlStatements 84 | SpaceInEmptyParentheses: false 85 | SpacesBeforeTrailingComments: 1 86 | SpacesInAngles: false 87 | SpacesInContainerLiterals: true 88 | SpacesInCStyleCastParentheses: false 89 | SpacesInParentheses: false 90 | SpacesInSquareBrackets: false 91 | Standard: Cpp11 92 | UseTab: Never 93 | ... 94 | -------------------------------------------------------------------------------- /src/example/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static zasm::Error createTestProgram64(zasm::Program& program) 7 | { 8 | using namespace zasm; 9 | 10 | // Imports. 11 | auto labelImpMessageBoxA = program.getOrCreateImportLabel("user32.dll", "MessageBoxA"); 12 | auto labelImpExitProcess = program.getOrCreateImportLabel("kernel32.dll", "ExitProcess"); 13 | 14 | // Labels. 15 | auto labelMain = program.createLabel("main"); 16 | auto labelStrMsgTitle = program.createLabel("str_MsgTitle"); 17 | auto labelStrMsg = program.createLabel("str_Msg"); 18 | 19 | x86::Assembler a(program); 20 | 21 | // .text 22 | a.section(".text", Section::Attribs::Code | Section::Attribs::Read | Section::Attribs::Exec); 23 | { 24 | // main: 25 | a.bind(labelMain); 26 | { 27 | // .stack_alloc 28 | a.sub(x86::rsp, Imm(40)); 29 | 30 | // invoke user32.MessageBoxA, NULL, labelStrMsgTitle, labelStrMsg, MB_OK 31 | a.mov(x86::rcx, Imm(0)); // HWND hWnd 32 | a.lea(x86::rdx, x86::qword_ptr(labelStrMsg)); // LPCTSTR lpText 33 | a.lea(x86::r8, x86::qword_ptr(labelStrMsgTitle)); // LPCTSTR lpCaption, 34 | a.mov(x86::r9, Imm(0)); // MB_OK 35 | a.call(x86::qword_ptr(labelImpMessageBoxA)); 36 | 37 | // invoke kernel32.ExitProcess 38 | a.xor_(x86::rcx, x86::rcx); 39 | a.call(x86::qword_ptr(labelImpExitProcess)); 40 | 41 | // .stack_free 42 | a.add(x86::rsp, Imm(40)); 43 | a.ret(); 44 | } 45 | } 46 | 47 | // .rdata 48 | a.section(".rdata", Section::Attribs::RData | Section::Attribs::Read); 49 | { 50 | // str_MsgTitle: 51 | a.bind(labelStrMsgTitle); 52 | a.embed("MessageBox Title"); 53 | 54 | // str_Msg: 55 | a.bind(labelStrMsg); 56 | a.embed("Hello World, this program was generated by zasm and LIEF."); 57 | } 58 | 59 | // Specify entrypoint. 60 | program.setEntryPoint(labelMain); 61 | 62 | return Error::None; 63 | } 64 | 65 | static zasm::Error buildTestPE64() 66 | { 67 | using namespace zasm; 68 | 69 | // Generate a new program. 70 | Program program(zasm::MachineMode::AMD64); 71 | if (auto err = createTestProgram64(program); err != Error::None) 72 | { 73 | return err; 74 | } 75 | 76 | // Create a new module. 77 | auto pe64Mod = modules::createModule(modules::ModuleType::PE, program, "test_pe64"); 78 | assert(pe64Mod != nullptr); 79 | 80 | if (auto err = pe64Mod->serialize(); err != Error::None) 81 | { 82 | return err; 83 | } 84 | 85 | std::filesystem::path outputPath("test_pe64.exe"); 86 | if (auto err = pe64Mod->save(outputPath); err != Error::None) 87 | { 88 | return err; 89 | } 90 | 91 | return Error::None; 92 | } 93 | 94 | int main() 95 | { 96 | if (auto err = buildTestPE64(); err != zasm::Error::None) 97 | { 98 | std::cout << "Failed to create PE64\n"; 99 | return -1; 100 | } 101 | 102 | return 0; 103 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is automatically generated from cmake.toml - DO NOT EDIT 2 | # See https://github.com/build-cpp/cmkr for more information 3 | 4 | cmake_minimum_required(VERSION 3.15) 5 | 6 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) 7 | message(FATAL_ERROR "In-tree builds are not supported. Run CMake from a separate directory: cmake -B build") 8 | endif() 9 | 10 | # Regenerate CMakeLists.txt automatically in the root project 11 | set(CMKR_ROOT_PROJECT OFF) 12 | if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 13 | set(CMKR_ROOT_PROJECT ON) 14 | 15 | # Bootstrap cmkr 16 | include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT) 17 | if(CMKR_INCLUDE_RESULT) 18 | cmkr() 19 | endif() 20 | 21 | # Enable folder support 22 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 23 | endif() 24 | 25 | # Create a configure-time dependency on cmake.toml to improve IDE support 26 | if(CMKR_ROOT_PROJECT) 27 | configure_file(cmake.toml cmake.toml COPYONLY) 28 | endif() 29 | 30 | project(zasm-modules 31 | LANGUAGES 32 | CXX 33 | ) 34 | 35 | # thirdparty 36 | set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER}) 37 | if(CMAKE_FOLDER) 38 | set(CMAKE_FOLDER "${CMAKE_FOLDER}/thirdparty") 39 | else() 40 | set(CMAKE_FOLDER thirdparty) 41 | endif() 42 | add_subdirectory(thirdparty) 43 | set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER}) 44 | 45 | # Target zasm-modules 46 | set(CMKR_TARGET zasm-modules) 47 | set(zasm-modules_SOURCES "") 48 | 49 | list(APPEND zasm-modules_SOURCES 50 | "src/zasm-modules/module.cpp" 51 | "src/zasm-modules/module.pe.cpp" 52 | "src/zasm-modules/module.pe.hpp" 53 | "include/zasm/modules/module.hpp" 54 | ) 55 | 56 | list(APPEND zasm-modules_SOURCES 57 | cmake.toml 58 | ) 59 | 60 | set(CMKR_SOURCES ${zasm-modules_SOURCES}) 61 | add_library(zasm-modules STATIC) 62 | 63 | if(zasm-modules_SOURCES) 64 | target_sources(zasm-modules PRIVATE ${zasm-modules_SOURCES}) 65 | endif() 66 | 67 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${zasm-modules_SOURCES}) 68 | 69 | add_library(zasm::modules ALIAS zasm-modules) 70 | target_compile_features(zasm-modules PUBLIC 71 | cxx_std_17 72 | ) 73 | 74 | target_include_directories(zasm-modules PUBLIC 75 | include 76 | ) 77 | 78 | target_link_libraries(zasm-modules PUBLIC 79 | zasm::common 80 | zasm::zasm 81 | LIEF::LIEF 82 | ) 83 | 84 | unset(CMKR_TARGET) 85 | unset(CMKR_SOURCES) 86 | 87 | # Target example 88 | set(CMKR_TARGET example) 89 | set(example_SOURCES "") 90 | 91 | list(APPEND example_SOURCES 92 | "src/example/main.cpp" 93 | ) 94 | 95 | list(APPEND example_SOURCES 96 | cmake.toml 97 | ) 98 | 99 | set(CMKR_SOURCES ${example_SOURCES}) 100 | add_executable(example) 101 | 102 | if(example_SOURCES) 103 | target_sources(example PRIVATE ${example_SOURCES}) 104 | endif() 105 | 106 | get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT) 107 | if(NOT CMKR_VS_STARTUP_PROJECT) 108 | set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT example) 109 | endif() 110 | 111 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${example_SOURCES}) 112 | 113 | target_compile_features(example PRIVATE 114 | cxx_std_17 115 | ) 116 | 117 | target_link_libraries(example PRIVATE 118 | zasm::common 119 | zasm::modules 120 | ) 121 | 122 | unset(CMKR_TARGET) 123 | unset(CMKR_SOURCES) 124 | 125 | -------------------------------------------------------------------------------- /src/zasm-modules/module.pe.cpp: -------------------------------------------------------------------------------- 1 | #include "module.pe.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace zasm::modules 7 | { 8 | static LIEF::PE::PE_TYPE getPEType(const Program& program) 9 | { 10 | auto peType = LIEF::PE::PE_TYPE::PE32; 11 | if (program.getMode() == zasm::MachineMode::AMD64) 12 | peType = LIEF::PE::PE_TYPE::PE32_PLUS; 13 | return peType; 14 | } 15 | 16 | ModulePE::ModulePE(const Program& program, const char* name) 17 | : _program(program) 18 | , _binary(name, getPEType(program)) 19 | { 20 | } 21 | 22 | static Error serializeProgramToBinary(zasm::Serializer& serializer, const Program& program, LIEF::PE::Binary& binary) 23 | { 24 | const uint64_t imageBase = 0x00400000; 25 | const uint64_t sectRVA = 0x1000; 26 | 27 | if (auto err = serializer.serialize(program, imageBase + sectRVA); err != zasm::Error::None) 28 | { 29 | return err; 30 | } 31 | 32 | const uint8_t* codeBuffer = serializer.getCode(); 33 | 34 | binary.optional_header().imagebase(imageBase); 35 | 36 | // Create the sections. 37 | for (size_t i = 0; i < serializer.getSectionCount(); ++i) 38 | { 39 | const auto* sectInfo = serializer.getSectionInfo(i); 40 | assert(sectInfo != nullptr); 41 | 42 | std::vector sectBuf; 43 | sectBuf.resize(sectInfo->physicalSize); 44 | 45 | if (sectInfo->physicalSize > 0) 46 | { 47 | std::memcpy(sectBuf.data(), codeBuffer + sectInfo->offset, sectInfo->physicalSize); 48 | } 49 | 50 | LIEF::PE::Section newSect(sectInfo->name); 51 | newSect.content(sectBuf); 52 | newSect.virtual_address(sectInfo->address - imageBase); // RVA 53 | newSect.virtual_size(sectInfo->virtualSize); 54 | if ((sectInfo->attribs & zasm::Section::Attribs::Code) != zasm::Section::Attribs::None) 55 | newSect.add_characteristic(LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_CNT_CODE); 56 | if ((sectInfo->attribs & zasm::Section::Attribs::Exec) != zasm::Section::Attribs::None) 57 | newSect.add_characteristic(LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_MEM_EXECUTE); 58 | if ((sectInfo->attribs & zasm::Section::Attribs::Read) != zasm::Section::Attribs::None) 59 | newSect.add_characteristic(LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_MEM_READ); 60 | if ((sectInfo->attribs & zasm::Section::Attribs::Write) != zasm::Section::Attribs::None) 61 | newSect.add_characteristic(LIEF::PE::SECTION_CHARACTERISTICS::IMAGE_SCN_MEM_WRITE); 62 | 63 | binary.add_section(newSect); 64 | } 65 | 66 | return Error::None; 67 | } 68 | 69 | static Error serializeImports(zasm::Serializer& serializer, const Program& program, LIEF::PE::Binary& binary) 70 | { 71 | std::vector>> imports; 72 | 73 | const auto addImport = [&](const char* mod, const char* func) { 74 | auto itGroup = std::find_if( 75 | imports.begin(), imports.end(), [&](auto&& entry) { return strcmp(entry.first, mod) == 0; }); 76 | if (itGroup == imports.end()) 77 | { 78 | imports.emplace_back(mod, std::vector{ func }); 79 | } 80 | else 81 | { 82 | auto& group = itGroup->second; 83 | auto itEntry = std::find_if(group.begin(), group.end(), [&](const char* fn) { return strcmp(fn, func) == 0; }); 84 | if (itEntry == group.end()) 85 | { 86 | group.push_back(func); 87 | } 88 | } 89 | }; 90 | 91 | // Build the import table by its used references, unused imports will not be added. 92 | for (size_t i = 0; i < serializer.getExternalRelocationCount(); ++i) 93 | { 94 | const zasm::RelocationInfo* relocData = serializer.getExternalRelocation(i); 95 | if (relocData->label != zasm::Label::Id::Invalid) 96 | { 97 | auto labelData = program.getLabelData(zasm::Label{ relocData->label }); 98 | if ((labelData->flags & zasm::LabelFlags::Import) == zasm::LabelFlags::None) 99 | continue; 100 | 101 | addImport(labelData->moduleName, labelData->name); 102 | } 103 | } 104 | 105 | // Generate the imports in the binary. 106 | std::sort(imports.begin(), imports.end(), [](auto&& lhs, auto&& rhs) { return strcmp(lhs.first, rhs.first) < 0; }); 107 | for (auto& [mod, entries] : imports) 108 | { 109 | auto& lib = binary.add_library(mod); 110 | 111 | std::sort(entries.begin(), entries.end(), [](auto&& lhs, auto&& rhs) { return strcmp(lhs, rhs) < 0; }); 112 | for (const char* func : entries) 113 | { 114 | lib.add_entry(func); 115 | } 116 | } 117 | 118 | // Fix the references. 119 | for (size_t i = 0; i < serializer.getExternalRelocationCount(); ++i) 120 | { 121 | const zasm::RelocationInfo* relocData = serializer.getExternalRelocation(i); 122 | if (relocData->label != zasm::Label::Id::Invalid) 123 | { 124 | auto labelData = program.getLabelData(zasm::Label{ relocData->label }); 125 | if ((labelData->flags & zasm::LabelFlags::Import) == zasm::LabelFlags::None) 126 | continue; 127 | 128 | const uint32_t funcRVA = binary.predict_function_rva(labelData->moduleName, labelData->name); 129 | const uint64_t funcVA = binary.imagebase() + funcRVA; 130 | 131 | if (relocData->kind == RelocationType::Rel32) 132 | { 133 | uint64_t rel = funcVA - (relocData->address + 4); 134 | uint32_t rel32 = static_cast(rel); 135 | 136 | binary.patch_address(relocData->address, rel, 4, LIEF::Binary::VA_TYPES::VA); 137 | } 138 | } 139 | } 140 | 141 | return Error::None; 142 | } 143 | 144 | static Error serializeExports(zasm::Serializer& serializer, const Program& program, LIEF::PE::Binary& binary) 145 | { 146 | // Currently unsupported. 147 | return Error::None; 148 | } 149 | 150 | static Error serializeRelocations(zasm::Serializer& serializer, const Program& program, LIEF::PE::Binary& binary) 151 | { 152 | // Currently unsupported. 153 | return Error::None; 154 | } 155 | 156 | static Error serializeEntryPoints(zasm::Serializer& serializer, const Program& program, LIEF::PE::Binary& binary) 157 | { 158 | auto entryLabel = program.getEntryPoint(); 159 | if (entryLabel.isValid()) 160 | { 161 | const auto entryVA = serializer.getLabelAddress(entryLabel.getId()); 162 | if (entryVA != 0) 163 | { 164 | const auto entryRVA = entryVA - binary.imagebase(); 165 | binary.optional_header().addressof_entrypoint(static_cast(entryRVA)); 166 | } 167 | } 168 | 169 | return Error::None; 170 | } 171 | 172 | Error ModulePE::serialize() 173 | { 174 | zasm::Serializer serializer; 175 | 176 | // Program 177 | if (auto err = serializeProgramToBinary(serializer, _program, _binary); err != Error::None) 178 | { 179 | return err; 180 | } 181 | 182 | // Imports 183 | if (auto err = serializeImports(serializer, _program, _binary); err != Error::None) 184 | { 185 | return err; 186 | } 187 | 188 | // Exports 189 | if (auto err = serializeExports(serializer, _program, _binary); err != Error::None) 190 | { 191 | return err; 192 | } 193 | 194 | // Relocations 195 | if (auto err = serializeRelocations(serializer, _program, _binary); err != Error::None) 196 | { 197 | return err; 198 | } 199 | 200 | // Relocations 201 | if (auto err = serializeEntryPoints(serializer, _program, _binary); err != Error::None) 202 | { 203 | return err; 204 | } 205 | 206 | return Error::None; 207 | } 208 | 209 | Error ModulePE::save(const std::filesystem::path& filePath) 210 | { 211 | LIEF::PE::Builder builder(_binary); 212 | 213 | builder.build_imports(); 214 | builder.build(); 215 | builder.write(filePath.string()); 216 | 217 | return Error::None; 218 | } 219 | 220 | } // namespace zasm::modules -------------------------------------------------------------------------------- /cmkr.cmake: -------------------------------------------------------------------------------- 1 | include_guard() 2 | 3 | # Change these defaults to point to your infrastructure if desired 4 | set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE) 5 | set(CMKR_TAG "v0.2.13" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) 6 | set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE) 7 | 8 | # To bootstrap/generate a cmkr project: cmake -P cmkr.cmake 9 | if(CMAKE_SCRIPT_MODE_FILE) 10 | set(CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}/build") 11 | set(CMAKE_CURRENT_BINARY_DIR "${CMAKE_BINARY_DIR}") 12 | file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}") 13 | endif() 14 | 15 | # Set these from the command line to customize for development/debugging purposes 16 | set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable") 17 | set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation") 18 | set(CMKR_BUILD_TYPE "Debug" CACHE STRING "cmkr build configuration") 19 | mark_as_advanced(CMKR_REPO CMKR_TAG CMKR_COMMIT_HASH CMKR_EXECUTABLE CMKR_SKIP_GENERATION CMKR_BUILD_TYPE) 20 | 21 | # Disable cmkr if generation is disabled 22 | if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION) 23 | message(STATUS "[cmkr] Skipping automatic cmkr generation") 24 | unset(CMKR_BUILD_SKIP_GENERATION CACHE) 25 | macro(cmkr) 26 | endmacro() 27 | return() 28 | endif() 29 | 30 | # Disable cmkr if no cmake.toml file is found 31 | if(NOT CMAKE_SCRIPT_MODE_FILE AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") 32 | message(AUTHOR_WARNING "[cmkr] Not found: ${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") 33 | macro(cmkr) 34 | endmacro() 35 | return() 36 | endif() 37 | 38 | # Convert a Windows native path to CMake path 39 | if(CMKR_EXECUTABLE MATCHES "\\\\") 40 | string(REPLACE "\\" "/" CMKR_EXECUTABLE_CMAKE "${CMKR_EXECUTABLE}") 41 | set(CMKR_EXECUTABLE "${CMKR_EXECUTABLE_CMAKE}" CACHE FILEPATH "" FORCE) 42 | unset(CMKR_EXECUTABLE_CMAKE) 43 | endif() 44 | 45 | # Helper macro to execute a process (COMMAND_ERROR_IS_FATAL ANY is 3.19 and higher) 46 | function(cmkr_exec) 47 | execute_process(COMMAND ${ARGV} RESULT_VARIABLE CMKR_EXEC_RESULT) 48 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 49 | message(FATAL_ERROR "cmkr_exec(${ARGV}) failed (exit code ${CMKR_EXEC_RESULT})") 50 | endif() 51 | endfunction() 52 | 53 | # Windows-specific hack (CMAKE_EXECUTABLE_PREFIX is not set at the moment) 54 | if(WIN32) 55 | set(CMKR_EXECUTABLE_NAME "cmkr.exe") 56 | else() 57 | set(CMKR_EXECUTABLE_NAME "cmkr") 58 | endif() 59 | 60 | # Use cached cmkr if found 61 | if(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}") 62 | set(CMKR_DIRECTORY_PREFIX "$ENV{CMKR_CACHE}") 63 | string(REPLACE "\\" "/" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}") 64 | if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$") 65 | set(CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}/") 66 | endif() 67 | # Build in release mode for the cache 68 | set(CMKR_BUILD_TYPE "Release") 69 | else() 70 | set(CMKR_DIRECTORY_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_") 71 | endif() 72 | set(CMKR_DIRECTORY "${CMKR_DIRECTORY_PREFIX}${CMKR_TAG}") 73 | set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}") 74 | 75 | # Handle upgrading logic 76 | if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE) 77 | if(CMKR_EXECUTABLE MATCHES "^${CMAKE_CURRENT_BINARY_DIR}/_cmkr") 78 | if(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}") 79 | message(AUTHOR_WARNING "[cmkr] Switching to cached cmkr: '${CMKR_CACHED_EXECUTABLE}'") 80 | if(EXISTS "${CMKR_CACHED_EXECUTABLE}") 81 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 82 | else() 83 | unset(CMKR_EXECUTABLE CACHE) 84 | endif() 85 | else() 86 | message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") 87 | unset(CMKR_EXECUTABLE CACHE) 88 | endif() 89 | elseif(DEFINED ENV{CMKR_CACHE} AND EXISTS "$ENV{CMKR_CACHE}" AND CMKR_EXECUTABLE MATCHES "^${CMKR_DIRECTORY_PREFIX}") 90 | message(AUTHOR_WARNING "[cmkr] Upgrading cached '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") 91 | unset(CMKR_EXECUTABLE CACHE) 92 | endif() 93 | endif() 94 | 95 | if(CMKR_EXECUTABLE AND EXISTS "${CMKR_EXECUTABLE}") 96 | message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'") 97 | elseif(CMKR_EXECUTABLE AND NOT CMKR_EXECUTABLE STREQUAL CMKR_CACHED_EXECUTABLE) 98 | message(FATAL_ERROR "[cmkr] '${CMKR_EXECUTABLE}' not found") 99 | elseif(NOT CMKR_EXECUTABLE AND EXISTS "${CMKR_CACHED_EXECUTABLE}") 100 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 101 | message(STATUS "[cmkr] Found cached cmkr: '${CMKR_EXECUTABLE}'") 102 | else() 103 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 104 | message(VERBOSE "[cmkr] Bootstrapping '${CMKR_EXECUTABLE}'") 105 | 106 | message(STATUS "[cmkr] Fetching cmkr...") 107 | if(EXISTS "${CMKR_DIRECTORY}") 108 | cmkr_exec("${CMAKE_COMMAND}" -E rm -rf "${CMKR_DIRECTORY}") 109 | endif() 110 | find_package(Git QUIET REQUIRED) 111 | cmkr_exec("${GIT_EXECUTABLE}" 112 | clone 113 | --config advice.detachedHead=false 114 | --branch ${CMKR_TAG} 115 | --depth 1 116 | ${CMKR_REPO} 117 | "${CMKR_DIRECTORY}" 118 | ) 119 | if(CMKR_COMMIT_HASH) 120 | execute_process( 121 | COMMAND "${GIT_EXECUTABLE}" checkout -q "${CMKR_COMMIT_HASH}" 122 | RESULT_VARIABLE CMKR_EXEC_RESULT 123 | WORKING_DIRECTORY "${CMKR_DIRECTORY}" 124 | ) 125 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 126 | message(FATAL_ERROR "Tag '${CMKR_TAG}' hash is not '${CMKR_COMMIT_HASH}'") 127 | endif() 128 | endif() 129 | message(STATUS "[cmkr] Building cmkr (using system compiler)...") 130 | cmkr_exec("${CMAKE_COMMAND}" 131 | --no-warn-unused-cli 132 | "${CMKR_DIRECTORY}" 133 | "-B${CMKR_DIRECTORY}/build" 134 | "-DCMAKE_BUILD_TYPE=${CMKR_BUILD_TYPE}" 135 | "-DCMAKE_UNITY_BUILD=ON" 136 | "-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}" 137 | "-DCMKR_GENERATE_DOCUMENTATION=OFF" 138 | ) 139 | cmkr_exec("${CMAKE_COMMAND}" 140 | --build "${CMKR_DIRECTORY}/build" 141 | --config "${CMKR_BUILD_TYPE}" 142 | --parallel 143 | ) 144 | cmkr_exec("${CMAKE_COMMAND}" 145 | --install "${CMKR_DIRECTORY}/build" 146 | --config "${CMKR_BUILD_TYPE}" 147 | --prefix "${CMKR_DIRECTORY}" 148 | --component cmkr 149 | ) 150 | if(NOT EXISTS ${CMKR_EXECUTABLE}) 151 | message(FATAL_ERROR "[cmkr] Failed to bootstrap '${CMKR_EXECUTABLE}'") 152 | endif() 153 | cmkr_exec("${CMKR_EXECUTABLE}" version) 154 | message(STATUS "[cmkr] Bootstrapped ${CMKR_EXECUTABLE}") 155 | endif() 156 | execute_process(COMMAND "${CMKR_EXECUTABLE}" version 157 | RESULT_VARIABLE CMKR_EXEC_RESULT 158 | ) 159 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 160 | message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding") 161 | endif() 162 | 163 | # Use cmkr.cmake as a script 164 | if(CMAKE_SCRIPT_MODE_FILE) 165 | if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake.toml") 166 | execute_process(COMMAND "${CMKR_EXECUTABLE}" init 167 | RESULT_VARIABLE CMKR_EXEC_RESULT 168 | ) 169 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 170 | message(FATAL_ERROR "[cmkr] Failed to bootstrap cmkr project. Please report an issue: https://github.com/build-cpp/cmkr/issues/new") 171 | else() 172 | message(STATUS "[cmkr] Modify cmake.toml and then configure using: cmake -B build") 173 | endif() 174 | else() 175 | execute_process(COMMAND "${CMKR_EXECUTABLE}" gen 176 | RESULT_VARIABLE CMKR_EXEC_RESULT 177 | ) 178 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 179 | message(FATAL_ERROR "[cmkr] Failed to generate project.") 180 | else() 181 | message(STATUS "[cmkr] Configure using: cmake -B build") 182 | endif() 183 | endif() 184 | endif() 185 | 186 | # This is the macro that contains black magic 187 | macro(cmkr) 188 | # When this macro is called from the generated file, fake some internal CMake variables 189 | get_source_file_property(CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" CMKR_CURRENT_LIST_FILE) 190 | if(CMKR_CURRENT_LIST_FILE) 191 | set(CMAKE_CURRENT_LIST_FILE "${CMKR_CURRENT_LIST_FILE}") 192 | get_filename_component(CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) 193 | endif() 194 | 195 | # File-based include guard (include_guard is not documented to work) 196 | get_source_file_property(CMKR_INCLUDE_GUARD "${CMAKE_CURRENT_LIST_FILE}" CMKR_INCLUDE_GUARD) 197 | if(NOT CMKR_INCLUDE_GUARD) 198 | set_source_files_properties("${CMAKE_CURRENT_LIST_FILE}" PROPERTIES CMKR_INCLUDE_GUARD TRUE) 199 | 200 | file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_PRE) 201 | 202 | # Generate CMakeLists.txt 203 | cmkr_exec("${CMKR_EXECUTABLE}" gen 204 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 205 | ) 206 | 207 | file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_POST) 208 | 209 | # Delete the temporary file if it was left for some reason 210 | set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt") 211 | if(EXISTS "${CMKR_TEMP_FILE}") 212 | file(REMOVE "${CMKR_TEMP_FILE}") 213 | endif() 214 | 215 | if(NOT CMKR_LIST_FILE_SHA256_PRE STREQUAL CMKR_LIST_FILE_SHA256_POST) 216 | # Copy the now-generated CMakeLists.txt to CMakerLists.txt 217 | # This is done because you cannot include() a file you are currently in 218 | configure_file(CMakeLists.txt "${CMKR_TEMP_FILE}" COPYONLY) 219 | 220 | # Add the macro required for the hack at the start of the cmkr macro 221 | set_source_files_properties("${CMKR_TEMP_FILE}" PROPERTIES 222 | CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" 223 | ) 224 | 225 | # 'Execute' the newly-generated CMakeLists.txt 226 | include("${CMKR_TEMP_FILE}") 227 | 228 | # Delete the generated file 229 | file(REMOVE "${CMKR_TEMP_FILE}") 230 | 231 | # Do not execute the rest of the original CMakeLists.txt 232 | return() 233 | endif() 234 | # Resume executing the unmodified CMakeLists.txt 235 | endif() 236 | endmacro() 237 | --------------------------------------------------------------------------------