cmake,将外部程序的结果作为预处理器定义传递

cmake, pass result of external program as preprocessor definitions

我是 cmake 的新手,所以如果我把事情搞砸了请纠正我,这应该使用 cmake 以外的其他方法来解决。

我有 main_program,需要在构建阶段以 bindata 的形式指定多个其他 subprograms。现在我通过 运行ning

构建它

cmake -DBINDATA1="\xde\xad..." -DBINDATA2="\xbe\xef" -DBINDATA3="..."

在代码中我将它们用作:

// main_program.cpp
int main() {
#ifdef BINDATA1
    perform_action1(BINDATA1);
#endif
#ifdef BINDATA2
    perform_action2(BINDATA2);
#endif
[...]

这是相当不干净的方法,因为任何时候我要更改其中之一 subprograms 我必须从中生成 bindata 并将其传递给 cmake 命令。

我想做的是有一个项目结构:

/
-> main_program
-> subprograms
   -> subprogram1
   -> subprogram2
   -> subprogram3

当我 运行 cmake 时,我想

  1. 编译每个 subprograms
  2. 通过 运行ning generate_bindata 程序在它们上面
  3. 从它们中的每一个生成 shellcode
  4. 构建main_program 从第 2 步传递 bindatas

and when I run cmake, I would like to

  • compile each of subprograms
  • generate shellcode from each of them, by running generate_shellcode program on them
  • build main_program passing shellcodes from step 2

那我们就开始吧。让我们先写一个简短的脚本来生成一个 header:

#!/bin/sh
# ./custom_script.sh

# TODO: Find out proper quoting and add `"` is necessarily. Ie. details.
# Prefer to use actual real variables like `static const char *shellcode[3]` 
# instead of raw macro defines.

cat > "" <<EOF
#define SHELLCODE1 $(cat "")
#define SHELLCODE2 $(cat "")
#define SHELLCODE3 $(cat "")
EOF

为了便于移植,请在cmake 中编写此脚本。此脚本将在构建阶段 运行 生成编译所需的 header。然后,“模型依赖关系”——找出究竟是什么依赖于什么。然后在cmake中写入:

add_executable(subprogram1 sources.c...)
add_executable(subprogram2 sources.c...)
add_executable(subprogram3 sources.c...)

for(i IN ITEMS 1 2 3)
    add_custom_target(
         COMMENT Generate shellcode${i}.txt with the content of shellcode
         # TODO: redirection in COMMAND should be removed, or the command 
         # should be wrapped in `sh -c ...`.             
         COMMAND $<TARGET_FILE:subprogram${i}> | generate_shellcode > ${CMAKE_CURRENT_BINARY_DIR}/shellcode${i}.txt
         OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/shellcode${i}.txt
         DEPENDS $<TARGET_FILE:subprogram${i}> generate_shellcode
    )
endfor()


add_custom_command(
    COMMENT Generate shellcodes.h from shellcode1.txt shellcode2.txt and shellcode3.txt
    COMMAND sh custom_script.sh 
         ${CMAKE_CURRENT_BINARY_DIR}/shellcodes.h
         ${CMAKE_CURRENT_BINARY_DIR}/shellcode1.txt
         ${CMAKE_CURRENT_BINARY_DIR}/shellcode2.txt
         ${CMAKE_CURRENT_BINARY_DIR}/shellcode3.txt
     OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/shellcodes.h
     DEPENDS
         ${CMAKE_CURRENT_BINARY_DIR}/shellcode1.txt
         ${CMAKE_CURRENT_BINARY_DIR}/shellcode2.txt
         ${CMAKE_CURRENT_BINARY_DIR}/shellcode3.txt
)

# Then compile the final executable
add_executable(main main.c ${CMAKE_CURRENT_BINARY_DIR}/shellcodes.h)
# Don't forget to add includes!
target_include_directories(main PUBLIC ${CMAKE_CURRENT_BINARY_DIR})

# or you may add dependency to a single file instead of target
# Like below only to a single shellcodeswrapper.c file only
# This should help build parallelization.
set_source_files_properties(main.c OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/shellcodes.h)

# Or you may add a target for shelcodes header file and depend on it
add_custom_target(shellcodes DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/shellcodes.h)
add_executable(main main.c)
target_include_directories(main PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
add_dependencies(main shellcodes)

然后是你的主文件:

#include <shellcodes.h>  // compiler will find it in BINARY_DIR
int main() {
    perform_action1(SHELLCODE1);
    perform_action2(SHELLCODE2);
}

为了避免每次都重新编译所有源文件,我建议写一个包装器:

// shellcodewrapper.c
#include <shellcodes.h>
// preserve memory by not duplicating code in each TU
static const char shellcode1[] = SHELLCODE1;
// only this file will be recompiled when SHELLCODE changes
const char *get_shellcode1(void) {
     return shellcode1;
}

// shellcodewrapper.h 
const char *get_shellcode1(void);

// main.c
#include <shellcodewrapper.h>
int main() {
    perform_action1(get_shellcode1());
    perform_action2(get_shellcode2());
}

这样当您更改“SHELLCODE”生成器时,只会编译 shellcodewrapper.c,从而实现超快的编译时间。

注意依赖关系是如何传输的以及它是如何工作的——我在 BINARY_DIR 中使用 files 将结果从一个命令传输到另一个命令,然后这些 files 跟踪更改的内容并在链下方传输依赖项。跟踪 DEPENDS 中的依赖项和 add_custom_command 中的 OUTPUT 并且 cmake 将以正确的顺序正确编译。