patterncMinor
Makefile for building ASM/C project
Viewed 0 times
makefileasmbuildingprojectfor
Problem
I am building a bare bones freestanding kernel in ASM and C. My make file is below.
This makefile is used as a child makefile.
My primary concern is that I cannot pass multiple files to NASM, so I need a rule for each assembly file I have. My secondary concern is cross dependencies on other files, for instance, my C files should be rebuild if any C header files change.
CFLAGS= -ffreestanding -nostdlib -gdwarf-4 -m32 -ggdb3 -Wall -Wextra -Werror -std=c11
ASMCFLAGS= -f elf32 -F dwarf -g -w+all
LDFLAGS= --nmagic -m elf_i386 --script=os.lds
BUILD_DIR=../build/os
BUILD_DIR_IMM=$(BUILD_DIR)/imm
BUILD_DIR_IMM_ASM=$(BUILD_DIR_IMM)/asm
BUILD_DIR_IMM_C=$(BUILD_DIR_IMM)/c
BUILD_OUTPUT=$(BUILD_DIR)/os
OS_C_SRCS := $(wildcard *.c)
OS_C_OBJS := $(patsubst %.c, $(BUILD_DIR_IMM_C)/%.o, $(OS_C_SRCS))
OS_ASM_SRCS := $(wildcard *.asm)
OS_ASM_OBJS := $(patsubst %.asm, $(BUILD_DIR_IMM_ASM)/%.o, $(OS_ASM_SRCS))
OS_SRCS := $(OS_C_SRCS) $(OS_ASM_SRCS)
OS_OBJS := $(OS_ASM_OBJS) $(OS_C_OBJS)
all: $(BUILD_OUTPUT)
prepdir:
mkdir -p $(BUILD_DIR_IMM_C)
mkdir -p $(BUILD_DIR_IMM_ASM)
$(OS_C_OBJS): $(OS_C_SRCS) $(OS_ASM_OBJS) prepdir
$(CC) $(CFLAGS) -c %%CODEBLOCK_0%%lt; -o $@
$(BUILD_DIR_IMM_ASM)/__init.o: __init.asm prepdir
nasm $(ASMCFLAGS) __init.asm -o $@
$(BUILD_DIR_IMM_ASM)/kinit.o: kinit.asm prepdir
nasm $(ASMCFLAGS) kinit.asm -o $@
$(BUILD_DIR_IMM_ASM)/keyboard.o: keyboard.asm prepdir
nasm $(ASMCFLAGS) keyboard.asm -o $@
$(BUILD_DIR_IMM_ASM)/interrupt.o: interrupt.asm prepdir
nasm $(ASMCFLAGS) interrupt.asm -o $@
$(BUILD_DIR_IMM_ASM)/string.o: string.asm prepdir
nasm $(ASMCFLAGS) string.asm -o $@
$(BUILD_DIR_IMM_ASM)/vid.o: vid.asm prepdir
nasm $(ASMCFLAGS) vid.asm -o $@
$(BUILD_OUTPUT): $(OS_C_OBJS) $(OS_ASM_OBJS)
ld $(LDFLAGS) $(OS_OBJS) -o $@
clean:
rm -rf $(BUILD_DIR)This makefile is used as a child makefile.
My primary concern is that I cannot pass multiple files to NASM, so I need a rule for each assembly file I have. My secondary concern is cross dependencies on other files, for instance, my C files should be rebuild if any C header files change.
Solution
I see a number of things that may help you improve this.
Don't manually write a
Since you're using standard tools such as
configure.ac
Makefile.am
With these, you can execute
Another tool I use very often is
With that said, if you're determined to do it the hard way, there are some things you can improve in your existing file.
Define an implicit rule
GNU Make (which I assume that you're using) knows how to create object files from assembly files, but only by using
This is a simple rule that tells
However, even this is often not necessary if you've defined the implicit rule. The next suggestion explains.
Make
The purpose to having
Then simply tell
Don't include paths in filenames
Inserting and maintaining paths everywhere is tedious and error prone. Better is to use
Now assuming your directory tree looks like this:
You can navigate into the empty
Mark phony targets
There isn't really an
Explicitly name sources
Lines like this one:
generally should be avoided. The reason is that if you create, say, a small test file to try out a concept while working on the source and then forget to delete it, it becomes part of the build whether you wanted it or not. It's usually far better to explicitly name files rather than using wildcards.
Putting it all together
Here's a revised
Makefile
```
CFLAGS= -ffreestanding -nostdlib -gdwarf-4 -m32 -ggdb3 -Wall -Wextra -Werror -std=c11
NASM=nasm
NASMFLAGS= -f elf32 -F dwarf -g -w+all
LDFLAGS= --nmagic -m elf_i386 -L$(VPATH) --script=os.lds
VPATH=../src
OBJECT_FILES=foo.o thing1.o thing2.o
.PHONY: all
all: os
%.o : %.asm
Don't manually write a
MakefileSince you're using standard tools such as
gcc and ld, you could use: autotools (that is the collection of autoconf and related software) instead of writing and maintaining your own Makefile. There is a learning curve, of course, but the benefits are considerable, especially if you're writing software that will become open source. To do that here, you can start with these two files:configure.ac
AC_PREREQ([2.69])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([foo.c])
AC_CONFIG_HEADERS([config.h])
AC_PROG_CC
PACKAGE_CFLAGS="-ffreestanding -nostdlib -gdwarf-4 -m32 -ggdb3 -Wall -Wextra -Werror -std=c11"
PACKAGE_LDFLAGS="--nmagic -m elf_i386 --script=os.lds"
PACKAGE_NASMFLAGS="-f elf32 -F dwarf -g -w+all"
AC_SUBST([PACKAGE_CFLAGS])
AC_SUBST([PACKAGE_LDFLAGS])
AC_SUBST([PACKAGE_NASMFLAGS])
AM_INIT_AUTOMAKE(foreign)
AC_CONFIG_FILES([Makefile])
AC_OUTPUTMakefile.am
bin_PROGRAMS = os
os_SOURCES = foo.c ${asm_sources}
os_NASMFLAGS = @PACKAGE_NASMFLAGS@
os_LDFLAGS = @PACKAGE_LDFLAGS@
AM_CPPFLAGS = @PACKAGE_CFLAGS@
NASM = /usr/bin/nasm
asm_sources = thing1.asm thing2.asm
.asm.o:
$(NASM) $(os_NASMFLAGS) %%CODEBLOCK_1%%lt; -o $@With these, you can execute
autoreconf -i in the same directory. Next, create a build directory anywhere (one of the advantages of automake) and execute the generated configure file from that directory. That will create a nice Makefile for you which you can then execute in the usual way by typing make.Another tool I use very often is
CMake, which does something similar to autotools in that it creates build scripts for you, but it's not tied to Linux and BSD and can work with Windows-based compilers as well.With that said, if you're determined to do it the hard way, there are some things you can improve in your existing file.
Define an implicit rule
GNU Make (which I assume that you're using) knows how to create object files from assembly files, but only by using
as rather than nasm. It's very simple to teach it a new trick, however, by defining an implicit rule. Here's one:.asm.o:
$(NASM) $(NASMFLAGS) %%CODEBLOCK_2%%lt; -o $@This is a simple rule that tells
make that the "recipe" to create a .o file from a .asm file is to run the command listed below. That way you only need to list a dependency and not the rule each time. That is:thing1.o: thing1.asmHowever, even this is often not necessary if you've defined the implicit rule. The next suggestion explains.
Make
make work harderThe purpose to having
make is to offload some of the work of the programmer to quickly do only what's necessary to rebuild a new binary. You can make it work harder than you currently are, while making things easier to maintain and a bit shorter as well. If you're using GNU Make, you can define an implicit rule like this (note the slightly different syntax from the standard implicit rule listed above):%.o : %.asm
$(NASM) $(NASMFLAGS) %%CODEBLOCK_4%%lt; -o $@Then simply tell
make the object files needed and let it figure out by itself that it needs to run the compiler on C files and nasm on assembly files:os : foo.o thing1.o thing2.o
ld $(LDFLAGS) $^ -o $@Don't include paths in filenames
Inserting and maintaining paths everywhere is tedious and error prone. Better is to use
VPATH within the Makefile like this:VPATH=../srcNow assuming your directory tree looks like this:
.
├── build
└── srcYou can navigate into the empty
build directory and type make -f ../src/Makefile. All of the binaries will be created in the build directory and the source directory will be untounched. VPATH tells make where to look for sources it needs. This is a much nicer way to do things. Mark phony targets
.PHONYThere isn't really an
all file created and there really isn't a clean file. These are actually just tags that identify a target whose rules should always be run. For this reason they should be explicitly declared as phony:.PHONY: clean
clean:
rm -rf os $(OBJECT_FILES)Explicitly name sources
Lines like this one:
OS_C_SRCS := $(wildcard *.c)generally should be avoided. The reason is that if you create, say, a small test file to try out a concept while working on the source and then forget to delete it, it becomes part of the build whether you wanted it or not. It's usually far better to explicitly name files rather than using wildcards.
Putting it all together
Here's a revised
Makefile which implements these suggestions:Makefile
```
CFLAGS= -ffreestanding -nostdlib -gdwarf-4 -m32 -ggdb3 -Wall -Wextra -Werror -std=c11
NASM=nasm
NASMFLAGS= -f elf32 -F dwarf -g -w+all
LDFLAGS= --nmagic -m elf_i386 -L$(VPATH) --script=os.lds
VPATH=../src
OBJECT_FILES=foo.o thing1.o thing2.o
.PHONY: all
all: os
%.o : %.asm
Code Snippets
AC_PREREQ([2.69])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([foo.c])
AC_CONFIG_HEADERS([config.h])
AC_PROG_CC
PACKAGE_CFLAGS="-ffreestanding -nostdlib -gdwarf-4 -m32 -ggdb3 -Wall -Wextra -Werror -std=c11"
PACKAGE_LDFLAGS="--nmagic -m elf_i386 --script=os.lds"
PACKAGE_NASMFLAGS="-f elf32 -F dwarf -g -w+all"
AC_SUBST([PACKAGE_CFLAGS])
AC_SUBST([PACKAGE_LDFLAGS])
AC_SUBST([PACKAGE_NASMFLAGS])
AM_INIT_AUTOMAKE(foreign)
AC_CONFIG_FILES([Makefile])
AC_OUTPUTbin_PROGRAMS = os
os_SOURCES = foo.c ${asm_sources}
os_NASMFLAGS = @PACKAGE_NASMFLAGS@
os_LDFLAGS = @PACKAGE_LDFLAGS@
AM_CPPFLAGS = @PACKAGE_CFLAGS@
NASM = /usr/bin/nasm
asm_sources = thing1.asm thing2.asm
.asm.o:
$(NASM) $(os_NASMFLAGS) $< -o $@.asm.o:
$(NASM) $(NASMFLAGS) $< -o $@thing1.o: thing1.asm%.o : %.asm
$(NASM) $(NASMFLAGS) $< -o $@Context
StackExchange Code Review Q#156479, answer score: 5
Revisions (0)
No revisions yet.