使用CMake为每一个C文件生成可执行对象

由于CLion似乎对CMake的友好度更高,所以将原有的使用Makefile的工作方式改为CMake。 原项目结构如下:

how2heap/
	Makefile
	{source_files}.c
	...
	glibc_2.25/
		{source_files}.c
	glibc_2.26/
		{source_files}.c

原有的Makefile内容如下,功能就是为每个C源文件单独生成一个可执行文件。

BASE = fastbin_dup malloc_playground first_fit calc_tcache_idx
V2.25 = glibc_2.25/fastbin_dup_into_stack glibc_2.25/fastbin_dup_consolidate glibc_2.25/unsafe_unlink glibc_2.25/house_of_spirit glibc_2.25/poison_null_byte glibc_2.25/house_of_lore glibc_2.25/overlapping_chunks glibc_2.25/overlapping_chunks_2 glibc_2.25/house_of_force glibc_2.25/large_bin_attack glibc_2.25/unsorted_bin_attack glibc_2.25/unsorted_bin_into_stack glibc_2.25/house_of_einherjar glibc_2.25/house_of_orange
V2.26 = glibc_2.26/unsafe_unlink glibc_2.26/house_of_lore glibc_2.26/overlapping_chunks glibc_2.26/large_bin_attack glibc_2.26/unsorted_bin_attack glibc_2.26/unsorted_bin_into_stack glibc_2.26/house_of_einherjar glibc_2.26/tcache_dup glibc_2.26/tcache_poisoning glibc_2.26/tcache_house_of_spirit
PROGRAMS = $(BASE) $(V2.25) $(V2.26)
CFLAGS += -std=c99 -g

# Convenience to auto-call mcheck before the first malloc()
#CFLAGS += -lmcheck

all: $(PROGRAMS)
clean:
	rm -f $(PROGRAMS)

如果要使用CMake的写法,那么有下面几步:

  1. 在所有存在源代码的目录下创建一个CMakeLists.txt,比如在how2heap,glibc_2.25和glibc_2.26这三个文件夹下各创建一个CMakeLists.txt。

  2. 编辑每个CMakeLists.txt,用来指导生成所在文件夹源文件对应的可执行文件。
  3. 使用CLion进行build等操作。

显然重点是第2步,下面分层次对CMakeLists.txt进行说明。

顶层CMakeLists.txt

项目根目录下的CMakeLists.txt如下所示:

project(how2heap)

cmake_minimum_required(VERSION 3.14)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -g")
set(CMAKE_VERBOSE_MAKEFILE TRUE)
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -lmcheck")


aux_source_directory(. topdir)

foreach(src ${topdir})
    string(REGEX MATCH "[A-Za-z0-9_]+" exe ${src})
    add_executable(${exe} ${src})
endforeach()


add_subdirectory(glibc_2.25)
add_subdirectory(glibc_2.26)

首先简单说明CMakeLists.txt中的变量定义与使用方法,变量定义直接写标识符即可,变量引用要写成${变量}这种形式。

只说重点,其中aux_source_directory用于将本CMakeLists.txt所在文件夹.(不包括子文件夹)下的所有源文件的路径以字符串构成的列表形式存进变量topdir

foreachendforeach语句写成Python就是for src in topdirstring函数正则匹配了源文件名无后缀的内容并赋值给变量exe

add_executable将可执行目标文件名(项目内不可重名)以及基于的源文件名注册进当前项目。

add_subdirectory表示CMake会处理指定的文件夹中的CMakeLists.txt。

子CMakeLists.txt

  1. 文件夹glibc_2.25中的CMakeLists.txt如下,相比顶层的CMakeLists.txt简洁许多。
aux_source_directory(. v25dir)  # v26dir in glibc_2.26

foreach(src ${v25dir})
    string(REGEX REPLACE "./([A-Za-z0-9_]+).c" "\\1_v25" exe ${src})
    add_executable(${exe} ${src})
endforeach()

由于在CMakeLists.txt中定义的变量似乎都是全局变量(来自经验,未查证),所以如果在这里aux_source_directory中还是写topdir而不是换一个名字比如v25dir,那么CMake会报类似找不到fastbin_dup.c(项目根目录下的一个文件)这种错误。

string函数选择了正则模式的替换功能:

  • 第一个正则式匹配了整个src变量,并且使用()语法将需要的可执行文件名提取了出来。

  • 第二个正则式使用\\1引用了上面使用()提取出来的内容,然后加上后缀。(由于glibc_2.25和glibc_2.26文件夹中存在很多文件名完全相同的文件,而add_executable不允许重名,所以分别在末尾加上_v25_v26加以区别而不是像顶层的CMakeLists.txt那样进行简单的匹配。)

  1. 文件夹glibc_2.26中的CMakeLists.txt类似,进行相应修改即可。

至此应该没有问题,可以使用CLion来build以及clean了。