Rob D
Rob D

Reputation: 111

cmake, pass result of external program as preprocessor definitions

I'm new to cmake, so correct me if I've messed things up and this should be solved using something other than cmake.

I have main_program, that requires multiple other subprograms in form of bindata to be specified at build phase. Right now I build it by running

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

and in code I use them as:

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

This is rather unclean method as any time I'm changing one of subprograms I have to generate bindata from it and pass it to cmake command.

What I would like to do, is have a project structure:

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

and when I run cmake, I would like to

  1. compile each of subprograms
  2. generate shellcode from each of them, by running generate_bindata program on them
  3. build main_program passing bindatas from step 2

Upvotes: 0

Views: 152

Answers (1)

KamilCuk
KamilCuk

Reputation: 141020

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

Then let's do that. Let's first write a short script to generate a 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 > "$1" <<EOF
#define SHELLCODE1 $(cat "$2")
#define SHELLCODE2 $(cat "$3")
#define SHELLCODE3 $(cat "$4")
EOF

To be portable, write this script in cmake. This script will be run at build phase to generate the header needed for compilation. Then, "model dependencies" - find out what depends on what exactly. Then write it in 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)

Then your main file:

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

So that all your source files are not recompiled each time, I suggest to write a wrapper:

// 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());
}

That way when you change the "SHELLCODE" generators, only shellcodewrapper.c will be compiled, resulting in super fast compilation times.

Note how dependency is transferred and how it works - I used files inside BINARY_DIR to transfer result from one command to another, then these files track what was changed and transfer dependency below in the chain. Track dependencies in DEPENDS and OUTPUT in add_custom_command and cmake will properly compile in proper order.

Upvotes: 1

Related Questions