An Interactive GNU Make Debugger

[article]
Summary:

The interactive GNU debugger has breakpoints, dumps information about the rule at which a breakpoint is hit and allows interactive querying of variable values and definitions.

The debugger has breakpoints, dumps information about the rule at which a breakpoint is hit and allows interactive querying of variable values and definitions. In a follow up article I'll extend the debugger to provide detailed tracing of the execution of any rule.

The Debugger In ActionBefore showing you how the debugger works, let's take a look at how to use it. The debugger and these examples all assume that you are using GNU Make 3.80 or above.

Here's an example Makefile that builds all from prerequisites foo and bar.

To illustrate the use of the debugger I've set a breakpoint in the all rule by inserting a line at the start of the rule's body that consists of just the variable $(__BREAKPOINT). $(__BREAKPOINT) will get expanded when the rule is ready to run and this will cause the debugger to break execution and prompt when the all rule is about to run. (I've ommitted all the debugger code, we'll take a look at it below).

MYVAR1 = hello 
 MYVAR2 = $(MYVAR1) everyone 
 all: MYVAR3 = $(MYVAR2) 
 all: foo bar 
     $(__BREAKPOINT) 
     @echo Finally making $@ 
 foo bar: 
     @echo Building $@

Here's what happens when this Makefile is executed with no existing files called all, foo or bar.

Building foo 
 Building bar 
 Makefile:51: GNU Make Debugger Break 
 Makefile:51: - Building 'all' from 'foo bar' 
 Makefile:51: - First prerequisite is 'foo' 
 Makefile:51: - Prequisites 'foo bar' are newer than 'all' 
 1>

First the output from the execution of the rules for foo and bar is seen (the Building foo and Building bar lines) and then there's a break into the debugger. The debugger break shows the line at which the break occurred and in which Makefile. In this case the breakpoint occurred at line 51 of Makefile. The debugger also outputs information about the rule being built. Here you can see that all is built from foo and bar; that the first prerequisite is foo (that's important because it's the first prerequisite and is stored in GNU Make's $< automatic variable. $< is typically used as the source code file name for compiles). The debugger also shows why the all rule ran: foo and bar are both newer than all (that's because they were both just build by their respective rules).

Finally the debugger prompts 1> for a command. The debugger will accept 32 commands before automatically continuing execution of the Makefile. The number 1 indicates that this is the first command; once 32> is reached the debugger will continue automatically. The first thing to do is ask for help by typing h.

1> h 
 Makefile:51: c       continue 
 Makefile:51: q       quit 
 Makefile:51: v VAR   print value of $(VAR) 
 Makefile:51: o VAR   print origin of $(VAR) 
 Makefile:51: d VAR   print definition of $(VAR) 
 2>

The debugger provides two means of stopping debugging: typing c continues with normal execution of the Makefile, typing q quits Make altogether. The three debugger commands v, o and d allow the user to interrogate GNU Make variables by asking for the value of a variable, its origin (where it was defined) or its definition. For example, the Makefile contains two variables MYVAR1 and MYVAR2 and a variable that is specific to the all rule: MYVAR3. A first step is to ask the debugger for the values of each of these variables:

2> v MYVAR1 
 Makefile:55: MYVAR1 has value 'hello' 
 3> v MYVAR2 
 Makefile:55: MYVAR2 has value 'hello everyone' 
 4> v MYVAR3 
 Makefile:55: MYVAR3 has value 'hello everyone' 
 5>

If it wasn't clear how MYVAR3 got its value we could ask the debugger for its definition:

5> d MYVAR3 
 Makefile:55: MYVAR3 is defined as '$(MYVAR2)' 
 6>

which shows that MYVAR3 is defined as $(MYVAR2). And so the obvious next step is to find out how MYVAR2 is defined (and also MYVAR1):

6> d MYVAR2 
 Makefile:55: MYVAR2 is defined as '$(MYVAR1) everyone' 
 7> d MYVAR1 
 Makefile:55: MYVAR1 is defined as 'hello' 
 8>

And if it wasn't clear where MYVAR1 got its value from the o command will show its origin:

8> o MYVAR1 
 Makefile:55: MYVAR1 came from file 
 9>

which means that MYVAR1 is defined in a Makefile. In contrast, if the user has overriden the value of MYVAR1 on the command line of Make (by running say make MYVAR1=Hello) the o command would reflect that:

1> v MYVAR1 
 Makefile:55: MYVAR1 has value 'Hello' 
 2> o MYVAR1 
 Makefile:55: MYVAR1 came from command line 
 3>

Breakpoints In PatternsAs well as setting breakpoints in normal rules, you can also set them in patterns. Every time that pattern rule is used the breakpoint is hit. For example,

all: foo.x bar.x 
  
 %.x: FOO = foo 
 %.x: %.y 
     $(__BREAKPOINT) 
     @echo Building $@ from $<... 
  
 foo.y: 
 bar.y:

Here all is built from foo.x and bar.x which requires building them from foo.y and bar.y using the %.x : %.y rule. I've inserted a breakpoint in the pattern rule and the debugger breaks twice: once for foo.x and once for bar.x:

Makefile:66: GNU Make Debugger Break 
 Makefile:66: - Building 'foo.x' from 'foo.y' 
 Makefile:66: - First prerequisite is 'foo.y' 
 Makefile:66: - Prerequisites 'foo.y' are newer than 'foo.x' 
 1> c 
 Building foo.x from foo.y... 
 Makefile:66: GNU Make Debugger Break 
 Makefile:66: - Building 'bar.x' from 'bar.y' 
 Makefile:66: - First prerequisite is 'bar.y' 
 Makefile:66: - Prerequisites 'bar.y' are newer than 'bar.x' 
 1> c 
 Building bar.x from bar.y...

Even pattern-specific variables work. %.x has a pattern-specific variable FOO with value foo the debugger v command can access it during a breakpoint on the pattern rule:

Makefile:67: GNU Make Debugger Break 
 Makefile:67: - Building 'foo.x' from 'foo.y' 
 Makefile:67: - First prerequisite is 'foo.y' 
 Makefile:67: - Prerequisites 'foo.y' are newer than 'foo.x' 
 1> v FOO 
 Makefile:67: FOO has value 'foo' 
 2>

Breakpoints In MakefilesFinally, you can simply stick a breakpoint in a Makefile if needed. Parsing of Makefiles will pause at the breakpoint and the current state of variables in the Makefile can be examined. For example with a breakpoint after each definition of FOO in this Makefile it's possible to see its value change:

FOO = foo 
 $(__BREAKPOINT) 
 FOO = bar 
 $(__BREAKPOINT) 
  
 Makefile:76: GNU Make Debugger Break 
 1> v FOO 
 Makefile:76: FOO has value 'foo' 
 2> c 
 Makefile:78: GNU Make Debugger Break 
 1> v FOO 
 Makefile:78: FOO has value 'bar' 
 2>

The DebuggerThe debugger itself draws on functions defined in the GNU Make Standard Library. The first line of the debugger includes the GMSL functions:

include gmsl 
  
 __LOOP := 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

The __PROMPT variable is used by the debugger to output the n> and read in a command followed by a single argument. __PROMPT uses the read command to get the command and argument into shell variables $CMD and $ARG and then returns a list of two elements: the first element is the command and the second the argument. Expanding __PROMPT prompts for an returns a single command and argument pair.

__PROMPT = $(shell read -p "$(__HISTORY)> " CMD ARG ; echo $$CMD $$ARG)

The __BREAK variable is used to get and handle a single command. First it stores the result of __PROMPT in __INPUT and the it calls the __DEBUG function (which handles debugger commands) with two arguments: the command and its argument returns by __PROMPT in __INPUT.

__BREAK = $(eval __INPUT := $(__PROMPT))                                    \ 
           $(call __DEBUG,                                                   \ 
               $(word 1,$(__INPUT)),                                         \ 
               $(word 2,$(__INPUT)))                        
 

The core of the debugger is handled by the __DEBUG function. __DEBUG takes a single character command in its first argument ($1) and an optional argument to the command in $2. $1 is stored in the variable __c, and $2 in __a. Then __DEBUG examines __c to see whether it is one of the supported debugger commands (c, q, v, d, o, or h) (if not a call to $(warning) will output an error message).

__DEBUG consists of a set of nested $(if) statements that use the GMSL seq function to determine is the __c is a valid debugger command. If it is then $(if)'s first argument is expanded, if not then the next $(if) is examined. For example, the v command (which outputs the value of a variable) is handled like this: $(if $(call seq,$(__c),v),$(warning $(__a) has value '$($(__a))'), ... next if ... ). If the __c command is v then $(warning) is used to output the value of the variable named by __a (the $($(__a)) outputs the value of the variable whose name is stored in __a).

When __DEBUG is done it returns either $(true) or $(false) (the empty string). $(true) indicates that the debugger show stop prompting for commands and continue execution. (The q command is handled by calling GNU Make's $(error) function to cause a fatal error, which stops the Make).

__DEBUG =  $(eval __c = $(strip $1))                                         \ 
            $(eval __a = $(strip $2))                                         \ 
            $(if $(call seq,$(__c),c),                                        \ 
               $(true),                                                       \ 
               $(if $(call seq,$(__c),q),                                     \ 
                  $(error Debugger terminated build),                         \ 
                  $(if $(call seq,$(__c),v),                                  \ 
                     $(warning $(__a) has value '$($(__a))'),                 \ 
                     $(if $(call seq,$(__c),d),                               \ 
                        $(warning $(__a) is defined as '$(value $(__a))'),    \ 
                        $(if $(call seq,$(__c),o),                            \ 
                           $(warning $(__a) came from $(origin $(__a))),      \ 
                           $(if $(call seq,$(__c),h),                         \ 
                              $(warning c       continue)                     \ 
                              $(warning q       quit)                         \ 
                              $(warning v VAR   print value of $$(VAR))       \ 
                              $(warning o VAR   print origin of $$(VAR))      \ 
                              $(warning d VAR   print definition of $$(VAR)), \ 
                              $(warning Unknown command '$(__c)'))))))) 
 

Finally, we come to the definition of __BREAKPOINT (the breakpoint variable we used in the example above). It first outputs a banner containing information (see __BANNER below), then it loops asking for commands by calling __BREAK. The loop terminates if it either runs out of items in __LOOP (which is where the 32 command limit is defined, see above), or if a call to __BREAK returns $(true)).

__BREAKPOINT = $(__BANNER)                                                   \ 
                $(eval __TERMINATE := $(false))                               \ 
                $(foreach __HISTORY,                                          \ 
                    $(__LOOP),                                                \ 
                    $(if $(__TERMINATE),,                                     \ 
                       $(eval __TERMINATE := $(__BREAK))))

__BANNER is used to show that the debugger has stopped at a breakpoint and by examining GNU Make automatic variables it is able to give information about the current rule being built.

__BANNER = $(warning GNU Make Debugger Break)                                \ 
            $(if $^,                                                          \ 
               $(warning - Building '$@' from '$^'),                          \ 
               $(warning - Building '$@'))                                    \ 
            $(if $<,$(warning - First prerequisite is '$<'))                  \ 
            $(if $%,$(warning - Archive target is '$%'))                      \ 
            $(if $?,$(warning - Prequisites '$?' are newer than '$@'))

ConclusionAnd there you have it. An interactive GNU Make debugger written entirely in GNU Make. Next time I'll build on this foundation to provide interactive tracing. In the meantime, I hope that this debugger helps you with your Makefile problems.

The Debugger for Copy and Paste

include gmsl 
  
 __LOOP := 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 
  
 __PROMPT = $(shell read -p "$(__HISTORY)> " CMD ARG ; echo $$CMD $$ARG) 
  
 __DEBUG =  $(eval __c = $(strip $1))                                         \ 
            $(eval __a = $(strip $2))                                         \ 
            $(if $(call seq,$(__c),c),                                        \ 
               $(true),                                                       \ 
               $(if $(call seq,$(__c),q),                                     \ 
                  $(error Debugger terminated build),                         \ 
                  $(if $(call seq,$(__c),v),                                  \ 
                     $(warning $(__a) has value '$($(__a))'),                 \ 
                     $(if $(call seq,$(__c),d),                               \ 
                        $(warning $(__a) is defined as '$(value $(__a))'),    \ 
                        $(if $(call seq,$(__c),o),                            \ 
                           $(warning $(__a) came from $(origin $(__a))),      \ 
                           $(if $(call seq,$(__c),h),                         \ 
                              $(warning c       continue)                     \ 
                              $(warning q       quit)                         \ 
                              $(warning v VAR   print value of $$(VAR))       \ 
                              $(warning o VAR   print origin of $$(VAR))      \ 
                              $(warning d VAR   print definition of $$(VAR)), \ 
                              $(warning Unknown command '$(__c)'))))))) 
  
 __BREAK = $(eval __INPUT := $(__PROMPT))                                     \ 
           $(call __DEBUG,                                                    \ 
               $(word 1,$(__INPUT)),                                          \ 
               $(word 2,$(__INPUT)))                         
  
 __BANNER = $(warning GNU Make Debugger Break)                                \ 
            $(if $^,                                                          \ 
               $(warning - Building '$@' from '$^'),                          \ 
               $(warning - Building '$@'))                                    \ 
            $(if $<,$(warning - First prerequisite is '$<'))                  \ 
            $(if $%,$(warning - Archive target is '$%'))                      \ 
            $(if $?,$(warning - Prequisites '$?' are newer than '$@')) 
  
 __BREAKPOINT = $(__BANNER)                                                   \ 
                $(eval __TERMINATE := $(false))                               \ 
                $(foreach __HISTORY,                                          \ 
                    $(__LOOP),                                                \ 
                    $(if $(__TERMINATE),,                                     \ 
                       $(eval __TERMINATE := $(__BREAK)))) 
 

About the author

AgileConnection is a TechWell community.

Through conferences, training, consulting, and online resources, TechWell helps you develop and deliver great software every day.