Monday, July 27, 2015

Using GDB with a target processor that does not have hardware breakpoints.

--This is an expansion of a previous blog GDB and Symbol Tables.--

GNU Debugger, GDB is one of the most popular debuggers today.

It has a lot of features. One of those features that doesn't have a lot discussion are the convenience variables and user-defined commands.

I work with firmware. GDB can be used with processors that are not the host processor where you write and compile your code. However, additional GDB commands must be used to provide this feature.

I found a very good use for both of these neglected features when working on firmware on legacy (old) hardware.

Motorla 68000

Microprocessors today support hardware breakpoints. The microprocessor provides these breakpoints as part of its structure, no additional support or tools required, JTAG enabled and all that rot.

But what happens when you have to work with legacy devices. The Motorola 68000 series microprocessor is such an example.

When the M68K was first introduced, the tool to use was an ICE, In-Circuit Emulator. A large connector, the same as the CPU, was inserted into the circuit board instead of the M68K, and an M68K was placed on top of this connector. The ICE device then controlled ALL the pins of the M68K. The ICE also had RAM that mirrored the memory space on the circuit board. In essence, a complete clone.

ICEs were expensive. One could still use an ICE today, if you can find one, and it is still working. :)

A simpler method was developed for the M68K, and later families of Motorola processors (today Freescale), BDM, Background Debug Mode.

BDM is a serial bus dedicated to debugging supported by a simple header.

Using BDM, one can read and write registers, read and write RAM, read from ROM and reflash programs, but it has very limited instruction debugging. This makes debugging complex programs difficult. BDM was a predecessor to JTAG.

Enter the convenience variables and user-defined commands.

GDB Server

To use GDB with any target hardware, one needs a GDB server to translate the GDB commands to those supported by the target hardware, along with sending the commands over a wire from the host to the target and back.

This is done in two ways, one is with a software GDB server, and one is with a hardware GDB server. With a software GDB server, a GDB server program is run on the host and it talks to the target via dedicated hardware. With a hardware GDB server, there is a hardware device that communicates with host, via Ethernet or RS-232 and has a built-in GDB server as well as its own set of commands. No software running on the host.

With either choice, you must inform GDB where the GDB server is located.

.GDBINIT

.gdbinit is the default file that GDB reads before it starts debugging your program. If the file doesn't exist, GDB doesn't complain about the missing file, but GDB won't talk to the hardware where you want to debug your program.

.gdbinit is a file that establishes the environment you want GDB to use with the target. You specify the DDR memory register values. You specify valid memory address ranges. You specify chip selects, etc. Anything that needs to be specified to be able to read and write to registers and memory of the target hardware.

Your program does all this, but GDB needs to talk to the hardware before your program starts.

In addition to configuring the remote target hardware, GDB needs to know where the hardware is.

target remote 192.168.0.10

This would be a typical command line added to .gdbinit, one of the first lines of the file.

One does not have use the file name .gdbinit. The command switch -x <filename> can be used to specify a file other than .gdbinit. On Linux systems, .gdbinit will be a hidden file. On Windows systems it is sometimes difficult to create a file with a period. [Notepad++ helps with this.]


Abatron BDI2000

Abatron, of Switzerland, produced a device, BDI2000, that supports the Motorola BDM and the CPU32 family of Motorola processors.

The Abatron BDI2000 is an example of a hardware GDB server.

Abatron BDI2000 User's Manual

[BTW, this device is no longer manufactured. Abatron has announced in 2015 that they will be closing their doors. There are several listings on eBay. ]

 The BDI2000 supports two native M68000 commands, ti and tc.

The ti command steps to the next machine instruction.

The tc command runs until there is a change in the flow of instructions. Essentially stop when there is a jump instruction.

The BDI2000 does not disassemble machine instructions. We use GDB to perform this task.

The Motorola 68332 starts at location 0. The first 32-bit value is top of the stack. The second 32-bit value is the address of the first instruction.

Upon 'reset' using the BDI2000, the 68332 program counter is at location 0x4.

How does one debug code written for this hardware?

How does one use GDB with such hardware?

You say "Can't you just use software breakpoints?". Yes, if the program is running out of RAM. If the program runs out of flash or EEProm, software breakpoints are not possible. A software break point requires the modification of the break location to replace the desired instruction with a BREAK interrupt instruction. Flash does not allow this substitution.

An alternative is to substitute a RAM chip for the Flash chip and download the program with each power on.

But let's say we don't want to change the hardware. We need to test and debug it the way it is.

Today with C/C++ compilers debugging in assembly is only for the most hard core programmers or difficult hardware bugs.

Convenience Variables and User-Defined Commands

Using convenience variables and user-defined commands, we are going to create break point commands, break on memory change, and other commands.

The GDB command that allows access to the native BDI2000 commands is monitor or mon for short.

To list the M68K registers, enter

 mon rd

GDB has the equivalent

  info reg

But mon rd has a very compact presentation. GDB presents each register on a separate line.

mon reset will perform a soft reset of the CPU.

The BDI2000 converts the GDB command si into ti and the ni into ti. The normal n and s commands do not work as they expect native hardware breakpoints or software breakpoints.

We will simulate s and n with user-defined commands. The user-defined commands are added to .gdbinit.

User-Define Commands

The format of a user-defined command is

define <command>
<body of command>
end
document <command>
<documentation>
end

The documentation written with the command is echoed when you type the GDB command help <command>.
All the user-defined commands are listed with the GDB command help user-defined.


Convenience Variables

A convenience variable is defined as

set <variable> = <expression>

Reset, Read, Info

Let's define three user-defined commands, mreset, mrd, and minfo.

define mreset
mon reset
flushregs
end
document mreset
Send the BDI2000 command 'reset'
'flushregs' is required as GDB does not
know what 'mon reset' did to the target
end

define mrd
mon rd
end
document mrd
Send the BDI2000 command 'rd'. Reads better than 'info reg'
end

define minfo
mon info
end
document minfo
Send the BDI2000 command 'info'
end

mreset is a short cut for the two commands mon reset and flushregs.

User-defined commands are very useful to define repetitive commands.

mrd is a repetitive command to use BDI2000 to output registers instead of GDB, as the output is more compact.

minfo is again another short cut command.

A more useful command is beefcore.

define beefcore
  mon mm 0xe00000 0xdeadbeef 0x10000
  mon mm 0xe10000 0xdeadbeef 0x10000
  mon mm 0xe20000 0xdeadbeef 0x10000
  mon mm 0xe30000 0xdeadbeef 0x10000
  mon mm 0xe40000 0xdeadbeef 0x10000
  mon mm 0xe50000 0xdeadbeef 0x10000
  mon mm 0xe60000 0xdeadbeef 0x10000
  mon mm 0xe70000 0xdeadbeef 0x10000
end
document beefcore
Fill RAM (0xe00000), 1MB, with 0xdeadbeef
end

Fills RAM with the same data, 0xdeadbeef.

Now that we done the easy commands, let's build more complicated ones.

dxi

First let's make a short cut dxi.

# dxi
# dxi <count>
# dxi <count> <startaddress>
define dxi
if $argc == 2
  x /$arg0i $arg1
end
if $argc == 1
  x /$arg0i $pc
end
if $argc == 0
  x /20i $pc
end
end
document dxi
Output $arg0 instructions from current PC
If $arg0 is not supplied, output 20 instructions
end

# starts a comment in .gdbinit.

The user-defined command supports if/then/else and while constructs.

Just like a function, arguments can be passed to a user-defined command. $argc is the variable with the number of arguments. Each argument is $argx where x is the argument number starting with zero.

The command dxi has three forms,

dxi

dxi <number>

dxi <number> <address>

$pc is the built-in GDB variable for the current program counter.

With no arguments, dni executes the GDB command x /20i $pc. Output 20 assembly instructions starting at the program counter.

dxi <number> changes the 20 to <number>. Notice this is a string substitution. <number> is not a number in the 'int' sense. It is a string that GDB interprets as a number.

Finally, dxi <number> <address> outputs <number> of assembly instructions starting at <address>.

dxi is a short cut command, but with some variability.

BMEM - Break on Change of Contents of An Address

Below are three command bmemc, bmems, and bmeml. bmemloop is used by all three other commands. This shows GDB user-defined commands support subroutines.

Each command executes an si command until the value at the specified address changes a specified number of times.

bmemc examines 8-bit values, bmems examines 16-bit values, and bmeml examines 32-bit values.

The logic of each command is the same.

Now the specific syntax of user-defined commands that is not well documented is shown.

#bmemloop ptr count
#internal function, called by bmemc, bmemw, and bmeml
define bmemloop
  if $argc == 2
     set $loop = $arg1
     set $bmemlooprd = *($arg0)
     set $bmemlooporg = *($arg0)
     while ($loop > 0)
       while ($bmemlooprd==$bmemlooporg)
         si
         set $bmemlooprd=*($arg0)
       end
       set $loop = $loop - 1
       if $loop == 0
         loop_break
       end
       set $bmemlooporg = *($arg0)
     end
  end
end
document bmemloop
First argument specified address to watch
Second argument specifies number of times to loop
  for each change of the specified address
bmemloop 10000 5 - Loop until address 10000 changes 5 times
end

define bmemc
  if $argc>=1
    disable disp 1
    set $bmemcx=(char*)($arg0)
    set $bmemccount = 1
    if $argc == 2
      set $bmemccount = $arg1
    end
    bmemloop $bmemcx $bmemccount
    enable disp 1
  end
end
document bmemc
bmemc runs until the byte at the address specified changes
end

define bmemw
  if $argc>=1
    disable disp 1
    set $bmemwx=(short*)($arg0)
    set $bmemwcount = 1
    if $argc == 2
      set $bmemwcount = $arg1
    end
    bmemloop $bmemwx $bmemwcount
    enable disp 1
  end
end
document bmemw
bmemw runs until the short (16-bit) at the address specified changes
end

define bmeml
  if $argc>=1
    disable disp 1
    set $bmemlx=(long*)($arg0)
    set $bmemlcount = 1
    if $argc == 2
      set $bmemlcount = $arg1
    end
    bmemloop $bmemlx $bmemlcount
    enable disp 1
  end
end
document bmeml
bmeml runs until the long (32-bit) at the address specified changes
end

Let's discuss some of the syntax.

set $bmemcx=(char*)($arg0)
set $bmemwx=(short*)($arg0)
set $bmemlx=(long*)($arg0)

These three lines each declare a convenience variable. Start with a dollar sign and create a name, start with a letter. The syntax that is not obvious is that the address specified as the first argument, arg0, of the command has to be casted to the appropriate type, a pointer of a specific width.

The syntax is the same as that of C.

The three convenience variables are now pointers.

We can pass the pointer to bmemloop, a subroutine, along with an optional count.

The enable and disable commands are used to suppress GDB output while the bmemloop is running. The si command outputs data each time it is executed.

To compare the value of two addresses we need pointers. bmemloop creates two convenience variables, bmemlooporg, the original value pointed to by arg0 and bmemlooprd, the value pointed to by arg0 after each si command.

bmemw 20000

This command will execute the si command until the 16-bit value at 20000 changes.

bmemc 0x30000 10

This command will execute the si command until the 8-bit value at 0x30000 changes 10 times.

Note that the address is hexadecimal. The cast to a pointer understands hexadecimal notation.

Performance

The commands bmemc, bmemw, and bmeml work, but they are slow. We are using the GDB interpreter to do something, that is today, done by hardware, but it does work.

Break on an Address - brk

Now let's create a break point user-defined command, brk.

# brk
# brk <dest_address>
# brk <dest_address> <count>
# brk <dest_address> <count> <offset>
define brk
  if $argc >= 1
    disable disp 1
    set $brkcount = 1
    if $argc >=2
      set $brkcount = $arg1
    end
    set $targ = (long)($arg0)
    if $argc == 3
      set $targ = $targ + $arg2
    end
    set $brkx = (unsigned char *)($targ)
    print $brkx
    if $brkcount > 0
      set $loop = 0
      while $brkx == $pc
        si
      end
      while ($loop < $brkcount)
        while ($brkx != $pc)
          si
        end
        set $loop = $loop +1
        if $loop == $brkcount
          loop_break
        end
        si
      end
    else
      print "Loop count cannot be negative"
    end
    enable disp 1
  end
  if $argc == 0
    si
  end
end
document brk
brk <dest_address> [count offset]
Send 'si' commands until <dest_address> reached
If count specified, send 'si' commands until <dest_address> is reached <count> times.
If offset specified, add <offset> to <dest_address> to make break address.
   When using <offset>, must specify count.
   brk $ReadSwitches 1 0x20 - break at $ReadSwitches+0x20 once.
end

The user-define command brk accepts one to three arguments. The first argument, mandatory, is the address we want to break on. The second optional argument is how many times we want to break before stopping. The third optional argument is an offset to add to the address to specify the break point address.

while ($brkx != $pc)

Execute si commands until the program counter is the same as the specified address.

Again the enable and disable commands are used.

brk is no speed demon, but it works.

The use of convenience variables and user-defined commands creates GDB commands that emulate break point commands.

See this bitbucket repository for all of the user-defined commands in one file.

Monday, July 20, 2015

Yet Another Makefile Tutorial - Part II

This blog continues where 'Yet Another Makefile Tutorial' left off. Remember sources are found at https://bitbucket.org/FernLeaf_07/makefiletutorial.

NOTE: The make files in the tutorial are NOT POSIX compliant.

We have a general purpose make file, makefile10a. The source is put in the source directory SRC_DIR. Object files are built in OBJ_DIR, and the program is built in TARGET. Includes files are found in SRC_DIR.

For a project with one flat directory of source, makefile10a does its job.

What about projects where there are sub-directories to SRC_DIR?

The problem with makefile10a is that it uses the make function wildcard to find the source files in SRC_DIR. wildcard does not recursively descend SRC_DIR looking for files. We need another function to perform the recursive search.

make does not have such a function, but we can use external commands. The external command to use is find.

BTW, vpath does not perform a recursive search for files. This is not a problem, yet.

find

find is a very complicated program. We are going to use a relatively simple form.

SRC_LIST := $(shell find $(SRC_DIR) -name "*.c" -type f)

find is started by shell. find is told to start the search for *.c files in the SRC_DIR. find recursively searches for files. -type f makes sure that find only retrieves files and not directories. It is rare that a directory could end in *.c, but -type f makes sure that directory is not included.

We update makefile10a to use find.

#makefile11
SRC_DIR := src
OBJ_DIR := obj
SRC_LIST := $(shell find $(SRC_DIR) -name "*.c" -type f)
OBJS := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC_LIST))
DEPS := $(OBJS:.o=.d)
TARGET_DIR := target
TARGET := $(TARGET_DIR)/aprogram
vpath %.c $(SRC_DIR)
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f $(OBJ_DIR)/*.o $(OBJ_DIR)/*.d
@rm -f $(TARGET)
@rmdir $(OBJ_DIR)
@rmdir $(TARGET_DIR)

$(TARGET): $(OBJS)
gcc $^ -o $@

$(OBJ_DIR)/%.o: %.c
gcc -c -MMD $< -o $@

REQUIRED_DIRS := $(OBJ_DIR) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
 do              \
 mkdir -p $$d;   \
 done)

-include $(DEPS)

Debugging Make Files

The debugging of make files is difficult. make does have a -d switch which is used for showing how make searches for targets and dependencies.

make does not have a switch to output its variables. The function info that will output trace messages while make is running.

$(info message)

We can use any string or variable to create message.

$(info SRC_LIST=$(SRC_LIST))
$(info OBJS=$(OBJS))
$(info DEPS=$(DEPS))

The function error can be substituted for the function info. When the function error is encountered, the message is output and make stops. warning is also available, but not needed for this debugging tutorial.

$(error STOP HERE!)

makefile11a is updated to use info.

#makefile11a
SRC_DIR := src
OBJ_DIR := obj
SRC_LIST := $(shell find $(SRC_DIR) -name "*.c" -type f)
OBJS := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC_LIST))
DEPS := $(OBJS:.o=.d)
$(info SRC_LIST=$(SRC_LIST))
$(info OBJS=$(OBJS))
$(info DEPS=$(DEPS))
TARGET_DIR := target
TARGET := $(TARGET_DIR)/aprogram
vpath %.c $(SRC_DIR)
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f $(OBJ_DIR)/*.o $(OBJ_DIR)/*.d
@rm -f $(TARGET)
@rmdir $(OBJ_DIR)
@rmdir $(TARGET_DIR)

$(TARGET): $(OBJS)
gcc $^ -o $@

$(OBJ_DIR)/%.o: %.c
gcc -c -MMD $< -o $@

REQUIRED_DIRS := $(OBJ_DIR) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
 do              \
 mkdir -p $$d;   \
 done)

-include $(DEPS)














Sub-Directories

We update our example by putting suba.c, subb.c, and subc.c files in the sub-directory sub. We also move all the include files to their own directory INC_DIR.

See updates in makefile12.

#makefile12 - does not work
SRC_DIR := src
OBJ_DIR := obj
INC_DIR := inc
SRC_LIST := $(shell find $(SRC_DIR) -name "*.c" -type f)
OBJS := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC_LIST))
DEPS := $(OBJS:.o=.d)
TARGET_DIR := target
TARGET := $(TARGET_DIR)/aprogram
vpath %.c $(SRC_DIR)
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f $(OBJ_DIR)/*.o $(OBJ_DIR)/*.d
@rm -f $(TARGET)
@rmdir $(OBJ_DIR)
@rmdir $(TARGET_DIR)

$(TARGET): $(OBJS)
gcc $^ -o $@

$(OBJ_DIR)/%.o: %.c
gcc -c -MMD -I $(INC_DIR) $< -o $@

REQUIRED_DIRS := $(OBJ_DIR) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
 do              \
 mkdir -p $$d;   \
 done)

-include $(DEPS)


Unfortunately, makefile12 do not work.








The problem is the old problem of directories do not exist that gcc expects to exist. In this case, the directory obj/sub does not exist.

find found all the source files, complete with sub-directory names, but there are no sub-directories in obj to match those in src.

How to solve this problem?

There are two possible solutions. First, find all the sub-directories in the SRC_LIST of files and create the corresponding sub-directories in OBJ_DIR, or remove all the sub-directory names from the file names in OBJS.

The first solution creates a mirror image of sub-directories in OBJ_DIR found in SRC_DIR.

The second solution will create a flat listing of object files in OBJ_DIR. No necessarily a bad solution.

Both solutions are acceptable. Both solutions shows using additional features of make.

Sub-Directories Under OBJ_DIR

First solution involves using the make functions patsubst, sort, and dir. We saw patsubst in the previous tutorial. sort and dir are new.

dir is a function that extracts the directory portion of a filename.

sort is a function that sorts a list and removes duplicates.

patsubst is the pattern substitution function.

SRC_DIR_LIST := $(patsubst %/,%,$(sort $(dir $(SRC_LIST))))

Let's explain the command from the inside out.

$(dir $(SRC_LIST))

This changes SRC_LIST from this

src/main.c src/suba/suba.c src/subb/subb.c src/subc/subc.c 

to this.

src/ src/sub/ src/sub/ src/sub/

There is one entry for each *.c file. We have the same sub-directory three times, in this example. If there were 50 files in a sub-directory, that sub-directory would appear 50 times.

sort is used to remove the duplicates. We really don't care about the sorting feature, we really just want the duplicates removed.

$(sort $(dir $(SRC_LIST)))

changes the SRC_LIST into this.

src/ src/sub/

Now we have a list of the sub directories.

Remove the trailing slash from the directory names. We will see why later.

$(patsubst %/,%,$(sort $(dir $(SRC_LIST))))

Finally, the list of directories where we find source files.

src src/sub

Change src to obj and we have the list directories for object files.

OBJ_DIR_LIST := $(patsubst $(SRC_DIR)%,$(OBJ_DIR)%,$(SRC_DIR_LIST))

Change how we create REQUIRED_DIRS and we are done.

REQUIRED_DIRS := $(OBJ_DIR_LIST) $(TARGET_DIR)

makefile13 shows all the updates for the first solution.

#makefile13
SRC_DIR := src
OBJ_DIR := obj
INC_DIR := inc
SRC_LIST := $(shell find $(SRC_DIR) -name "*.c" -type f)
SRC_DIR_LIST := $(patsubst %/,%,$(sort $(dir $(SRC_LIST))))
OBJ_DIR_LIST := $(patsubst $(SRC_DIR)%,$(OBJ_DIR)%,$(SRC_DIR_LIST))
OBJS := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC_LIST))
DEPS := $(OBJS:.o=.d)
TARGET_DIR := target
TARGET := $(TARGET_DIR)/aprogram
vpath %.c $(SRC_DIR)
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f $(OBJ_DIR)/*.o $(OBJ_DIR)/*.d
@rm -f $(TARGET)
@rmdir $(OBJ_DIR)
@rmdir $(TARGET_DIR)

$(TARGET): $(OBJS)
gcc $^ -o $@

$(OBJ_DIR)/%.o: %.c
gcc -c -MMD -I $(INC_DIR) $< -o $@

REQUIRED_DIRS := $(OBJ_DIR_LIST) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
 do              \
 mkdir -p $$d;   \
 done)

-include $(DEPS)







We solved the directory problem for the first solution. We now have a mirror image of sub-directories in the OBJ_DIR as is found in SRC_DIR.

Let's check the clean command.







The rm command does not remove directories recursively. We have a sub-directory under obj, sub. The sub directory has to be removed before we can remove obj.

rm has a switch to recursively delete directories recursively

@rm -rf $(OBJ_DIR)
@rm -rf $(TARGET_DIR)

Updating makefile13 the final make file is this.


#makefile13
SRC_DIR := src
OBJ_DIR := obj
INC_DIR := inc
SRC_LIST := $(shell find $(SRC_DIR) -name "*.c" -type f)
SRC_DIR_LIST := $(patsubst %/,%,$(sort $(dir $(SRC_LIST))))
OBJ_DIR_LIST := $(patsubst $(SRC_DIR)%,$(OBJ_DIR)%,$(SRC_DIR_LIST))
OBJS := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC_LIST))
DEPS := $(OBJS:.o=.d)
TARGET_DIR := target
TARGET := $(TARGET_DIR)/aprogram
vpath %.c $(SRC_DIR)
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f $(OBJ_DIR)/*.o $(OBJ_DIR)/*.d
@rm -f $(TARGET)
@rm -rf $(OBJ_DIR)
@rm -rf $(TARGET_DIR)

$(TARGET): $(OBJS)
gcc $^ -o $@

$(OBJ_DIR)/%.o: %.c
gcc -c -MMD -I $(INC_DIR) $< -o $@

REQUIRED_DIRS := $(OBJ_DIR_LIST) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
 do              \
 mkdir -p $$d;   \
 done)

-include $(DEPS)

makefile13 is the final make file. We have sources in SRC_DIR and its sub-directories. We have include files in INC_DIR. We generate object files in OBJ_DIR and mirror image sub-directories of SRC_DIR, and the target is generated in TARGET_DIR.

clean works correctly.

Sub-Directories - Second Solution, Flat OBJ_DIR

The second solution is to create a flat directory of object files.

We want the filenames from the SRC_LIST, not the directories.

The make function notdir extracts the filename, not the directory.

OBJS := $(patsubst %.c,$(OBJ_DIR)/%.o,$(notdir $(SRC_LIST)))

We now have a list of files object files.

obj/main.o obj/suba.o obj/subb.o obj/subc.o

Unfortunately changing OBJS in makefile12 produces this error.







The problem we have is that make can't find suba.c. It is in a sub-directory. The file name of the pattern is obj/suba.o, not obj/sub/suba.o which is changed to sub/suba.c. With vpath looking in SRC_DIR, the file suba.c is found in SRC_DIR/sub/suba.c.

We need to give vpath the list of source directories. Use SRC_DIR_LIST from makefile13.



SRC_DIR_LIST := $(patsubst %/,%,$(sort $(dir $(SRC_LIST))))

So update OBJS, add SRC_DIR_LIST to makefile12, update vpath, we get makefile14.

#makefile14
SRC_DIR := src
OBJ_DIR := obj
INC_DIR := inc
SRC_LIST := $(shell find $(SRC_DIR) -name "*.c" -type f)
SRC_DIR_LIST := $(patsubst %/,%,$(sort $(dir $(SRC_LIST))))
OBJS := $(patsubst %.c,$(OBJ_DIR)/%.o,$(notdir $(SRC_LIST)))
DEPS := $(OBJS:.o=.d)
$(info OBJS=$(OBJS))
TARGET_DIR := target
TARGET := $(TARGET_DIR)/aprogram
vpath %.c $(SRC_DIR_LIST)
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f $(OBJ_DIR)/*.o $(OBJ_DIR)/*.d
@rm -f $(TARGET)
@rmdir $(OBJ_DIR)
@rmdir $(TARGET_DIR)

$(TARGET): $(OBJS)
gcc $^ -o $@

$(OBJ_DIR)/%.o: %.c
gcc -c -MMD -I $(INC_DIR) $< -o $@

REQUIRED_DIRS := $(OBJ_DIR) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
 do              \
 mkdir -p $$d;   \
 done)


-include $(DEPS)

This update works.





Update makefile14 for clean the same as makefile13. The final result is this.

#makefile14
SRC_DIR := src
OBJ_DIR := obj
INC_DIR := inc
SRC_LIST := $(shell find $(SRC_DIR) -name "*.c" -type f)
SRC_DIR_LIST := $(patsubst %/,%,$(sort $(dir $(SRC_LIST))))
OBJS := $(patsubst %.c,$(OBJ_DIR)/%.o,$(notdir $(SRC_LIST)))
DEPS := $(OBJS:.o=.d)
TARGET_DIR := target
TARGET := $(TARGET_DIR)/aprogram
vpath %.c $(SRC_DIR_LIST)
.PHONY: all clean

all: $(TARGET)

clean:
@rm -f $(OBJ_DIR)/*.o $(OBJ_DIR)/*.d
@rm -f $(TARGET)
@rm -rf $(OBJ_DIR)
  @rm -rf $(TARGET_DIR)

$(TARGET): $(OBJS)
gcc $^ -o $@

$(OBJ_DIR)/%.o: %.c
gcc -c -MMD -I $(INC_DIR) $< -o $@

REQUIRED_DIRS := $(OBJ_DIR) $(TARGET_DIR)
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
 do              \
 mkdir -p $$d;   \
 done)

-include $(DEPS)

The second solution is now complete.

Summary

Fini.

We have two files, makefile13, and makefile14, that are general purpose make files for a project with sources in one directory, object files in another, and the target in a third directory.

Future

The make files handle just one source type *.c. What if you have *.cc, or *.cxx files? 

If you just have *.cc instead of *.c or *.cxx instead of *.c, then simply change the find command to use *.cc or *.cxx.

What if you have both *.c and *.cxx? Typically c files are compiled differently than *.cxx files. There are different switches supplied to gcc.

The next part in this blog on make files will look at updating makefile13 to handle multiple file extensions, groups of files that need different compilation, and excluding source files from the build.

See the next blog. Yet Another Make Tutorial III