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.