hhvn.uk

Website, gopher, etc
git clone https://hhvn.uk/hhvn.uk
git clone git://hhvn.uk/hhvn.uk
Log | Files | Refs | Submodules

makefile_workflow.txt (5220B)


      1 Title: Tips for workflows involving makefiles and gdb
      2 Date: 2022-11-22
      3 Updated: 2022-11-25 (valgrind)
      4 Tags: c make gdb ctags tags vim valgrind
      5 vim: set tw=77 cc=77 :
      6 
      7 # Some tips for making C projects more convenient.
      8 This integrates:
      9 - make
     10 - ctags (hence vim, or other editors that support tags)
     11 - gdb
     12 - valgrind
     13 
     14 Create a config.mk. This can keep your workflow related stuff in a seperate
     15 file from the makefile (that you'll probably track in git). This should then
     16 be included in the makefile.
     17 
     18 Of course, many projects will ship a config.mk already: usually these contain
     19 different CFLAGS, LDFLAGS, etc. tailored for different operating systems and
     20 architectures. They don't change often though, so I would recommend leaving
     21 your changes to a config.mk untracked, and manually adding changes you want
     22 to propagate elswhere. git add -p is your friend.
     23 
     24 ## Now, what can you do in this file?
     25 
     26 1. Debug flags. A lot of C programs will allow you to define a macro that
     27    makes them more verbose, amonst other things, making them ugly but easier
     28    to debug.
     29 
     30    A macro can be defined by passing the -D flag to a compiler. Usually flags
     31    used by the compiler are stored in $(CFLAGS) so this can be done with:
     32 
     33    	CFLAGS += -DDEBUG
     34 
     35    Something I find useful is replacing calls to exit() or similar on errors
     36    with SIGTRAP:
     37 
     38    	#ifdef DEBUG
     39 		raise(SIGTRAP)
     40 	#else
     41 		exit(1);
     42 	#endif
     43 
     44    SIGTRAP will cause the program to dump its core, which is useful as a
     45    debugger can inspect it to see what happened to the program. However, as I
     46    will show later in this file, it can also act as a breakpoint.
     47 
     48 2. Tagging. You can create a target that regenerates a tag file whenever source
     49    is edited pretty quickly:
     50 
     51    	all: tags
     52 
     53 	tags: $(SRC)
     54 		ctags -R .
     55 
     56 	.PHONY: all tags
     57 
     58    Make will run the ctags command whenever a dependency (in this case, all
     59    the source files stored in $(SRC)) is changed. The ctags command will
     60    recurse through the directory the makefile is, outputting all the tag
     61    information to a file named, aptly, 'tags'. This file is then read by your
     62    editor (if it's any good).
     63 
     64 3. Always running code via GDB.
     65 
     66    There are two different ways I might like to run some code.
     67 
     68    If I expect it to run fine, then I don't want to have to type 'run' in
     69    GDB, and then 'quit' once it finishes running - I want to being able to
     70    run it again quickly whenever any changes are made. In this case I use the
     71    following target:
     72 
     73    	run: all # 'all' is usually a phony target that builds everything
     74 		# You may want to put proper tests here as well.
     75 		gdb ./$(BIN) -ex 'set confirm on' -ex run -ex bt -ex quit
     76 		# -ex tells gdb to run a command, these are run in order
     77 
     78 	.PHONY: test
     79 
     80    If everything goes smoothly, once the program exits, gdb should too.
     81    However, if something goes wrong, say a SIGSEGV, or perhaps a SIGTRAP that
     82    was raised when exiting due to an error (remember earlier on?), it will
     83    print a backtrace and allow you to start poking around.
     84 
     85    Invoke this with `make run`
     86 
     87    ---
     88 
     89    In other cases though, you know that something is wrong, know roughly
     90    where it happened (perhaps due to the backtrace triggered when you ran
     91    `make test`) but need to figure out why. Here you'll probably want to set
     92    a backtrace before the program is run, in which case this target may be
     93    handy:
     94 
     95    	gdb: all
     96 		gdb ./$(BIN)
     97 
     98 	.PHONY: gdb
     99 
    100    Yeah, it's pretty simple, but better than typing it out or grabbing it
    101    from history. If you need to hand your program arguments, use --args. This
    102    can be placed in a macro for convenience:
    103 
    104    	ARGS = whatever you want
    105 
    106 	gdb: all
    107 		gdb --args ./(BIN) $(ARGS)
    108 
    109    And since it's a macro, you could append $(ARGS) to the 'test' target, and
    110    now you only need to change it in one place.
    111 
    112 4. Valgrind. This is a pretty memory-leak checker (amongst other things),
    113    though unfortunately it really slows down the program it's being run on.
    114    Hence it's probably a good idea to put it in a seperate target:
    115 
    116 	VALFILE = valgrind.log
    117 	VALSUPP = valgrind-suppress
    118 	memcheck: all
    119 		@echo Outputting to $(VALFILE)
    120 		valgrind --tool=memcheck --leak-check=full \
    121 			--suppressions=$(VALSUPP) --log-file=$(VALFILE) \
    122 			./$(BIN) $(ARGS)
    123 	
    124    By default, valgrind outputs to stdout. This can get pretty messy if your
    125    program also outputs to stdout or otherwise uses the terminal. The
    126    --log-file flag can be used to specify a file for valgrind to write to
    127    instead. This is useful even if you're writing a GUI program as scrolling
    128    up terminals (if your terminal even has scrollback) can be a pain compared
    129    to opening up a file in less (or vim, which is pretty much always better
    130    in my opinion).
    131 
    132    Another thing this target does is provide valgrind with suppressions.
    133    Creating another file is in order. You can suppress anything you want, but
    134    one good usecase is suppressing anything other than your own program, i,e,
    135    code run by libraries:
    136 
    137 	{
    138 		ignore_versioned_libs
    139 		Memcheck:Leak
    140 		...
    141 		obj:*/lib*/lib*.so
    142 	}
    143 	{
    144 		ignore_versioned_libs
    145 		Memcheck:Leak
    146 		...
    147 		obj:*/lib*/lib*.so.*
    148 	}
    149 
    150    An unfortunate side-effect of this is that any callbacks that you provide
    151    to the library won't be checked.