Author Topic: how do you debug the code?  (Read 16306 times)

0 Members and 1 Guest are viewing this topic.

Offline Dadu@Topic starter

  • Contributor
  • Posts: 34
  • Country: in
how do you debug the code?
« on: June 18, 2022, 11:10:48 am »
I don't understand what is the best way to debug c code. What is your general approach for any compiler to c debug code ?

Should I set a breakpoint after pressing the debug button or set a break point first and then press the debug button?

There is step in and step out button while debugging how to use it? should i stop the program then continue press step in button
 

Online Ian.M

  • Super Contributor
  • ***
  • Posts: 12860
Re: how do you debug the code?
« Reply #1 on: June 18, 2022, 12:37:21 pm »
That's going to be IDE, toolchain, hardware debugger and device dependent.   As you haven't told us what you are using the only applicable answer is "Do what works!"  |O

If you want a more general answer, how about:
42

 
The following users thanked this post: hexreader, Alti

Online brucehoult

  • Super Contributor
  • ***
  • Posts: 4035
  • Country: nz
Re: how do you debug the code?
« Reply #2 on: June 18, 2022, 02:33:44 pm »
I never use interactive debuggers.

It wasn't a good technique 40 years ago when CPUs ran 250 thousand instructions a second and I could press the "step" button realistically once or twice a second.  It's a MUCH WORSE technique today when desktop CPUs run in excess of 10 billion instructions a second, per core, and interact with the real world (other machines) in very timing-dependent ways.

Put printf() or some equivalent at interesting parts of your code, either simply saying "I am here now" or also printing the values of interesting variables.

Don't let anyone tell you that's a crude or beginner's way to debug. It's the professional's way.

The only reason to ever use an interactive debugger is perhaps if your problem is not that your program is misbehaving but that you simply don't understand how a programming language works.
 
The following users thanked this post: MikeK, Siwastaja, uer166, SiliconWizard, Picuino, AussieBruce, betocool, Dadu@

Online MK14

  • Super Contributor
  • ***
  • Posts: 4539
  • Country: gb
Re: how do you debug the code?
« Reply #3 on: June 18, 2022, 03:19:20 pm »
I don't understand what is the best way to debug c code. What is your general approach for any compiler to c debug code ?

Should I set a breakpoint after pressing the debug button or set a break point first and then press the debug button?

There is step in and step out button while debugging how to use it? should i stop the program then continue press step in button

Ignoring the politics, of if and when, debuggers should be used, here is an example of how to use a debugger.

Let's say you want to produce a list of prime numbers, and at some point, it starts outputting obviously wrong, negative numbers.  (Assuming your debugger has this feature), you can set the breakpoint, to be a conditional breakpoint, set to activate when prime < 0.

Run it, until the conditional breakpoint stops the code, then hopefully, by looking around the code (and variables values), can help you see what has gone wrong.  If not.  You can look at what the loop counter was set to when the prime variable became negative.  Let's say it had reached 10,001 when the apparent bug, occurs and primes suddenly became negative.

You can now set the conditional breakpoint, to be loop_counter >= 9,998.  Restart form the beginning and let it run, then use the single step features, to watch the prime variable become negative, and then hopefully see what has gone wrong.

It can get very fiddly and time-consuming, continually pressing the single-step (into) feature.  So, if you see it call a function, which you think is 100% perfect, and completely unrelated to the likely bug you're trying to find.  You can use the step-over feature, to keep in the same place, within the source file.  I.e. it will 'instantly' run the function, and return to the next line in the source code, without you needing to press the single-step (into), button/feature, perhaps hundreds of times.

N.B. If we really are talking about microcontrollers (the opening post seems to be rather short on specific details), then as previously suggested, printf, can be a better approach.
As well as printf, turning on or off, specific ports, possibly also connected to indicator LEDs.  Can also be helpful/necessary, especially if complicated peripheral setup conditions are really the issue.  It also means you can check the timings with a scope, as necessary.

As a rule of thumb, it might be best to think of using a debugger, as a tool of last resort.  Unless you are specifically trying to learn how to use them, or other possible reasons.
« Last Edit: June 18, 2022, 03:23:11 pm by MK14 »
 
The following users thanked this post: hans, Dadu@

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21681
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: how do you debug the code?
« Reply #4 on: June 18, 2022, 03:30:22 pm »
I never use interactive debuggers.

It wasn't a good technique 40 years ago when CPUs ran 250 thousand instructions a second and I could press the "step" button realistically once or twice a second.  It's a MUCH WORSE technique today when desktop CPUs run in excess of 10 billion instructions a second, per core, and interact with the real world (other machines) in very timing-dependent ways.

Put printf() or some equivalent at interesting parts of your code, either simply saying "I am here now" or also printing the values of interesting variables.

Don't let anyone tell you that's a crude or beginner's way to debug. It's the professional's way.

The only reason to ever use an interactive debugger is perhaps if your problem is not that your program is misbehaving but that you simply don't understand how a programming language works.

Indeed there's even merit in just logging everything you can get your fingers on.  This isn't so feasible with limited RAM or slow comms on a real time embedded system say, but when you are able to collect a wide swath of things, take advantage of your other tools -- dump it into a database, or run filters or regex or whatever on it -- look for outliers, race conditions, faulty calculations, etc.  Examples: function inputs/parameters, steps of calculations, interrupts, main() loop stuff, etc.  Perhaps including timestamps, if you have a reasonably useful timer/clock to use that way.

With enough bandwidth, you could log some megabytes of data very quickly (seconds)... which is a terrifying amount for just a little MCU or something, but a pitiful embarrassment to any PC in the last couple decades.

Compare with some of the debugging/monitoring tools on PCs themselves -- Windows procmon for example, hooks all system calls -- thousands per second, and who cares, you've got GB of RAM to hold onto all that!

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline nctnico

  • Super Contributor
  • ***
  • Posts: 26906
  • Country: nl
    • NCT Developments
Re: how do you debug the code?
« Reply #5 on: June 18, 2022, 04:05:48 pm »
I never use interactive debuggers.

It wasn't a good technique 40 years ago when CPUs ran 250 thousand instructions a second and I could press the "step" button realistically once or twice a second.  It's a MUCH WORSE technique today when desktop CPUs run in excess of 10 billion instructions a second, per core, and interact with the real world (other machines) in very timing-dependent ways.

Put printf() or some equivalent at interesting parts of your code, either simply saying "I am here now" or also printing the values of interesting variables.

Don't let anyone tell you that's a crude or beginner's way to debug. It's the professional's way.
The truth is in the middle. Printfs are definitely handy for printing status information. All my programs have those (and sometimes with the option to enable more debugging). Very valuable to get a good understanding what a program is doing in realtime.

However, interactive debugging is a good tool for finding elusive bugs like a pointer failure or a variable that somehow gets the wrong value. Being able to trace function calls back to their origin is very handy. Another good thing about interactive debugging is to verify code actually does what it is supposed to do. I use interactive debugging exclusively on a PC though when developing code that is supposed to run on a microcontroller later on.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 
The following users thanked this post: hans, peter-h, tooki, MK14, Fredderic, Dadu@

Offline coppice

  • Super Contributor
  • ***
  • Posts: 8646
  • Country: gb
Re: how do you debug the code?
« Reply #6 on: June 18, 2022, 04:42:06 pm »
I never use interactive debuggers.
I never use interactive debuggers for debugging desktop or server software. I do use them for small MCUs. There is so little potential for instrumenting the code in a very small machine, with limited I/O, that interactive debuggers can really help. Especially when the machine has some kind of build in logic analyser, so you can let a real time app run up an interesting point, and get a snapshot.

 
The following users thanked this post: MK14, WattsThat, Dadu@

Offline IanB

  • Super Contributor
  • ***
  • Posts: 11885
  • Country: us
Re: how do you debug the code?
« Reply #7 on: June 18, 2022, 05:03:12 pm »
I never use interactive debuggers.

On the contrary, I do use interactive debuggers.

There are two useful aspects. One is that you can inspect the call stack, and can examine more about the program state than just the values of variables. The second is that depending on what you see, you can examine the contents of variables not only in the current routine, but also in any function higher up the stack.

A third use of debuggers is that you can interactively evaluate expressions in response to what you see, so you can check things out. You can also adjust things before proceeding, including perhaps modifying code ("edit and continue"), to see if your proposed improvement is going to work.

This is not to say that print statements and logging facilities are not also useful. They are. But every tool has a place, and an effective tool users knows when and where to use every tool in the tool kit.
 
The following users thanked this post: hans, tooki, MK14, Fredderic

Offline mikerj

  • Super Contributor
  • ***
  • Posts: 3240
  • Country: gb
Re: how do you debug the code?
« Reply #8 on: June 18, 2022, 05:20:45 pm »
Put printf() or some equivalent at interesting parts of your code, either simply saying "I am here now" or also printing the values of interesting variables.

Don't let anyone tell you that's a crude or beginner's way to debug. It's the professional's way.

A professional uses whatever method is most appropriate rather than constraining themselves to one specific method.  e.g. printf can be an execution time hog that can change the behaviour of the system on slower micros.
 
The following users thanked this post: hans, Dave, ajb, splin, tooki, MK14, Dadu@

Offline pcprogrammer

  • Super Contributor
  • ***
  • Posts: 3700
  • Country: nl
Re: how do you debug the code?
« Reply #9 on: June 18, 2022, 05:31:33 pm »
A professional uses whatever method is most appropriate rather than constraining themselves to one specific method.  e.g. printf can be an execution time hog that can change the behaviour of the system on slower micros.

I second that.

For instance if you need to know timing of an interrupt without loosing a lot of cpu cycles toggle an io pin and hook up an oscilloscope to measure the time taken for the interrupt and the frequency of its occurrence.

When working on a MCU there is not always a serial connection to the PC, so printf won't do you any good. Or you are working bare metal and don't have printf because you don't want to use the libraries. Then Single Wire Debugging can be very helpful.

But you have to learn when to use which tool.

Offline Dadu@Topic starter

  • Contributor
  • Posts: 34
  • Country: in
Re: how do you debug the code?
« Reply #10 on: June 18, 2022, 05:41:19 pm »
That's going to be IDE, toolchain, hardware debugger and device dependent.

Mostly when we start learning C programming, first of all we practice by installing compiler IDE tool on PC. In my case Hardware is PC and IDE is Visual Studio 2022. Visual studio also has a debugger and I can't get the idea how to use this debugger. I can see the variable value but I can't see the exact place where the variable will store. of course i can use print statement but i want to look on debugger window
 

Offline IanB

  • Super Contributor
  • ***
  • Posts: 11885
  • Country: us
Re: how do you debug the code?
« Reply #11 on: June 18, 2022, 05:59:13 pm »
I can see the variable value but I can't see the exact place where the variable will store.

Do you mean that you want to see the place in the code where the value gets modified? For that, you can put a debug watch on the variable, so that the debugger will stop when any change is made to the value.

Or do you mean you want to see the memory address of the variable? That is easy to to, but why would you want to? How would it be useful?
 
The following users thanked this post: MK14

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6260
  • Country: fi
    • My home page and email address
Re: how do you debug the code?
« Reply #12 on: June 18, 2022, 06:05:46 pm »
I don't understand what is the best way to debug c code. What is your general approach for any compiler to c debug code ?
Eyeball mark 2.  The mark 2 is the version that understands that developer intent and what the code actually does are two independent things, and whenever they diverge, you are likely to end up with a bug.

I've written so much C, and read/gone through even more C code, that I do not usually need to run the binary in a debugger; I can usually detect the bugs with eyeball mark 2 just fine.  For my own code, I do need to sleep between writing the code and going through it with debugging-eyes; otherwise the human visual filtering machinery will intervene making me miss some of the errors.

I do often use both fprintf(stderr, "describe key variable values\n", key variables) at key position in the code, for example at the beginning of a function I'm working on, and things like Graphviz DOT format output for trees and graphs, that I can then easily visualize using e.g. dot (from Graphviz).

When I learned to write unit "tests" first (to examine low-level solutions, for example abstract data type implementations – like disjoint sets – and their accessor functions), and then incorporate the implementations one by one into a working program, testing frequently and fixing all bugs and implementing all error checks before going on to the next step, and compiling at each step with warnings enabled, I suddenly became much more productive with much fewer bugs.

As an example, whenever I start writing a new command-line tool, I start by writing a working skeleton that does nothing:
Code: [Select]
#include <stdlib.h>

int main(int argc, char *argv[])
{
    return EXIT_SUCCESS;
}
Then I add command-line parsing, usually using getopt() (POSIX) or getopt_long() (GNU extension):
Code: [Select]
#define _POSIX_C_SOURCE  200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <locale.h>
#include <getopt.h>
#include <stdio.h>

static void usage(const char *arg0)
{
    fprintf(stderr, "\n");
    fprintf(stderr, "Usage: %s [ -h | --help ]\n", arg0);
    fprintf(stderr, "       %s FILE...\n", arg0);
    fprintf(stderr, "\n");
}

int main(int argc, char *argv[])
{
    static const struct option  longopts[] = {
        { .name = "help", .has_arg = 0, .flag = NULL, .val = 'h' },
        { 0 }
    };
    const char *arg0 = (argc && argv && argv[0] && argv[0][0]) ? argv[0] : "(this)";
    int  opt;

    if (!setlocale(LC_ALL, ""))
        fprintf(stderr, "Warning: Current locale is not supported.\n");

    while ((opt = getopt_long(argc, argv, "h", longopts, NULL)) != -1) {
        switch (opt) {

            case 'h':
                usage(arg0);
                return EXIT_SUCCESS;

            default: /* '?', and catch-all for unimplemented but specified options */
                usage(arg0);
                return EXIT_FAILURE;
        }
    }

    /* Require at least one non-option argument */
    if (optind >= argc) {
        usage(arg0);
        return EXIT_FAILURE;
    }

    /* argv[optind] through argv[argc-1] are the non-option arguments. */

    /* Debug output: */
    for (int i = optind; i < argc; i++)
        fprintf(stderr, "Argument %d (%d): \"%s\".\n", i - optind + 1, i, argv[i]);

    return EXIT_SUCCESS;
}
Because I like efficiency (being lazy!), I also tend to apply my standard Makefile skeleton.  Let's assume the above is named example.c, and we want to compile it to ./ex.  The needed Makefile is then
Code: [Select]
CC      := gcc
CFLAGS  := -Wall -Wextra -O2
LDFLAGS := -lm
PROGS   := ex

.PHONY: all clean

all: $(PROGS)

clean:
rm -f *.o $(PROGS)

%.o: %.c
$(CC) $(CFLAGS) -c $^

ex: example.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
although the indent has to be with Tab characters which this forum converts to spaces, so you need to run sed -e 's|^  *|\t|' -i Makefile to fix the indentation.  (You can run it safely at any point, and I recommend doing so after modifying the Makefile).  Then, to recompile the program from scratch, you only need to run make clean all.  (Plain make only rebuilds modified sources; here, only if example.c has been modified.)

This way, whenever I write additional code, I already know that any compilation error is due to the code I've added since the latest successful build.  Before adding more code, I fix those errors.  It saves a LOT of time.

If I am not sure yet what data structure I should use, I create a separate minimal test program(s), and implement the data structure there.  For trees and graphs, I often generate random data, and output the tree or graph in DOT format, using a safe tree/graph traversal (usually using a flag, "already described" in each node) to avoid getting caught in a traversal loop, and visually examine a few dozen trees/graphs to see it works.  I then create the pathological cases (like what happens if the input is the same, in the worst possible order, and so on), and verify my code won't get confused.

When I've found a thing that works, I then transplant the needed code to my main project, and recompile and check.  Again, the key is implementing and verifying each part, before advancing to the next (writing any additional code), so that the amount of code one needs to check stays minimal.

I can categorically say that writing a full program without compiling it at any point, is the wrong approach for me at least.  It wastes time, because I'm not perfect.  If I leverage the compiler and especially its capability to warn about suspicious code, I don't need to worry much about typos – I do rely on man pages instead of memorizing standard C and POSIX C interfaces –, and concentrate on the more important things like the overall design and algorithms, I am both faster and generate fewer bugs.  At this point, I'm pretty sure I've written over half a million lines of C, although only a fraction of that as paid work.  (But I have worked on and provided bug-fixing patches to quite a few open source projects, including to the Linux kernel, the GNU standard library, and the GCC compiler suite.)
 
The following users thanked this post: RoGeorge, MK14, Old Printer

Offline hans

  • Super Contributor
  • ***
  • Posts: 1638
  • Country: nl
Re: how do you debug the code?
« Reply #13 on: June 18, 2022, 06:34:23 pm »
Don't feel bad to use a debugger. Printfs are an okay way to move forward.. it works.. having continuous trace logs of your program is useful for postmortems (for example if your program suddenly breaks, or only breaks once a week).

But beyond that scope, if you're using printfs to check where the program currently (still is), what values the local variables have, and then after verifying you remove it straight away.. we have modern GUI's for that, and they are called debuggers. Don't make people convince you it's a weakness to use them. It's an ease of use thing in my opinion. Do whatever works easiest for you. If you want to set breakpoints before launching the program, or after, pick what you like. Maybe some tools have quirks that it only works one way or the other.. then learn that quirk and move on.

Now there are some things printfs and debuggers can't do.

For one, if you hit a debugger breakpoint then any real-time system will crash. Protocols are also real-time, such as USB or TCP/IP, as the other side will eventually hit a timeout and drop the connection. This is less of a problem on PC debugging where you're working in "user land". The OS will, in the background, will service keep-alive stuff on an USB or TCP/IP level, so it's unlikely to time out there. But in embedded, especially with machinery equipment that is continuously processing values to stop a robot arm in time, I wouldn't want my code to hit any breakpoint.

Still that doesn't make a debugger connection useless. A debugger tool is so much more than just a code execution flow visualizer. On a lower level, JTAG/SWD debuggers read/write CPU registers and memory content. Accessing CPU data typically requires it to be halted, so in real-time we don't want that. But memory poking is still possible and can be useful to make low or 'zero' overhead printf tracers or data loggers. For examples you can take a look at e.g. Segger RTT or SystemView. Both allow data transport over the same interface as is used for programming. If you combine this with host-based printf formatting, then you can have rich a printf tracing experience with just a few hundred bytes of library code on the MCU, a few dozen CPU cycles per "printf" (copy straight into SRAM FIFO, where the host software will collect the data)... all running on the same few wires as your programming interface.

Finally, if you can afford to pause your program, then a debugger can also be a cheap profiling tool. If you want to profile a program without bloating the executable with instrumentation code, you can use a sampling based approach. There may be nice automated tools for that, but chances are.. those are desktop based, perhaps clunky to use or paid software. Instead, you can also do the sampling yourself by pausing-resuming the program a couple of times. Eventually you can get a pretty good idea what code is eating most of your CPU time, as you'll have a good chance to hit it most of the time.

I use both printf and debuggers off and on. Both have their strengths, and it's best to use them for that.
« Last Edit: June 18, 2022, 06:39:51 pm by hans »
 
The following users thanked this post: MK14

Offline nctnico

  • Super Contributor
  • ***
  • Posts: 26906
  • Country: nl
    • NCT Developments
Re: how do you debug the code?
« Reply #14 on: June 18, 2022, 06:46:11 pm »
One step further: what is very helpfull is to have counters in software. All my embedded projects have a command line interface that allows to query the status of various modules, do diagnostics (self test) and counters. For example: number of interrupts, number of characters / packets received / transmitted, number of errors. This is extremely handy to do remote diagnostics; having this feature saved huge amounts of time on multiple occasions. Some of my customers even use it to collect data for quality control purposes.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 
The following users thanked this post: MK14, emece67, tellurium

Offline jpanhalt

  • Super Contributor
  • ***
  • Posts: 3477
  • Country: us
Re: how do you debug the code?
« Reply #15 on: June 18, 2022, 06:52:07 pm »
I code in Assembly (MPASM), but my suggestion is not specific for that language.  Microchip gives 2 choices: a software simulation and hardware simulation with a limited number of breakpoints.  Software is good at finding stupid substitutions/typos, e.g., movlw v. movfw instructions.  Hardware is more useful with some peripherals (e.g, LCD's).  I add a 3rd type.  Flash an LED.  I can put a little routine anywhere I want and am basically only limited only by the number of "free" pins I may have available.
 
The following users thanked this post: MK14

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21681
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: how do you debug the code?
« Reply #16 on: June 18, 2022, 07:25:38 pm »
Some incidental features on newer MCUs are nice.  I did a cascade of PID loops recently and dumped the inner loop's output to a spare DAC channel -- DACs used to be quite rare on MCUs.  I can compensate the loop by updating parameters from debug console, and read the result immediately on the scope.

(I mostly use a debug console with some very rudimentary memory inspection/update commands, and various commands customized for the task, e.g. demoing a new peripheral configuration or algorithm or whatever. See console and commands files here: https://github.com/T3sl4co1l/Reverb )

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 
The following users thanked this post: MK14

Offline nctnico

  • Super Contributor
  • ***
  • Posts: 26906
  • Country: nl
    • NCT Developments
Re: how do you debug the code?
« Reply #17 on: June 18, 2022, 07:41:03 pm »
Some incidental features on newer MCUs are nice.  I did a cascade of PID loops recently and dumped the inner loop's output to a spare DAC channel -- DACs used to be quite rare on MCUs.  I can compensate the loop by updating parameters from debug console, and read the result immediately on the scope.
A PWM channel also does fine for that purpose! Having an oscilloscope that can do filtering is a big plus.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline peter-h

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Re: how do you debug the code?
« Reply #18 on: June 18, 2022, 08:11:57 pm »
The answer to the OP depends, as some have pointed out.

As nctnico says both breakpoints (with single stepping) and printfs have their uses. Both have limitations.

The former stops your program running (the CPU is basically stopped) but breakpoints are great for checking algorithms etc and basic code function, plus they are great for picking up elusive/rare faults, and then you get a stack trace which is usually useful. But you have to connect the debugger so your board needs to have the connector on it for that.

The latter doesn't stop the program running but will tend to introduce a long delay (printf is a huge chunk of code, even if the entire output string goes quickly into an interrupt driven TX buffer so you are not e.g. serial port baud rate limited; in this context people pay for debuggers which have a fast debugging port feature). And this method can be used without a debugger, via a serial port, USB VCP, ethernet (http server etc).


Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 
The following users thanked this post: MK14

Online brucehoult

  • Super Contributor
  • ***
  • Posts: 4035
  • Country: nz
Re: how do you debug the code?
« Reply #19 on: June 18, 2022, 11:24:26 pm »
Put printf() or some equivalent at interesting parts of your code, either simply saying "I am here now" or also printing the values of interesting variables.

Don't let anyone tell you that's a crude or beginner's way to debug. It's the professional's way.

A professional uses whatever method is most appropriate rather than constraining themselves to one specific method.  e.g. printf can be an execution time hog that can change the behaviour of the system on slower micros.

If you can't afford the timing changes from a printf() -- and it doesn't have to literally be printf(), it can be as simple as a unique byte put in a buffer or toggling pins on a GPIO port -- then how can you afford a breakpoint?
« Last Edit: June 18, 2022, 11:43:52 pm by brucehoult »
 
The following users thanked this post: hans

Online brucehoult

  • Super Contributor
  • ***
  • Posts: 4035
  • Country: nz
Re: how do you debug the code?
« Reply #20 on: June 18, 2022, 11:43:33 pm »
The latter doesn't stop the program running but will tend to introduce a long delay (printf is a huge chunk of code, even if the entire output string goes quickly into an interrupt driven TX buffer so you are not e.g. serial port baud rate limited

You don't have to do the actual formatting on the microcontroller!

The easiest quick&dirty thing is to link in a custom printf() function that doesn't actually format but just writes its arguments in binary to the output buffer, the first of those being the address of the format string.

Host-based software can then easily look up the format string in the ELF file and apply it to the arguments.

The only tricky thing about this is knowing how many arguments there are. You could just always dump 3 or 4 (especially if you write your code to limit the maximum number), or add a sentinel value to each call, or make printf1(), printf(2), printf3() etc functions that write a byte to say how many arguments there are. On host builds they can just map to normal printf().

More complex methods can allocate enums for each format string, either manually or using a source code pre-processing technique -- and not have the strings present in the microcontroller binary at all.
 
The following users thanked this post: hans, tellurium

Offline hans

  • Super Contributor
  • ***
  • Posts: 1638
  • Country: nl
Re: how do you debug the code?
« Reply #21 on: June 19, 2022, 07:28:49 am »
I agree that if printf breaks your system, then a breakpoint will do for sure. But with a breakpoint, I've pretty much accepted that this will happen and the system may need a reset. With printf it can be more subtle. For example, let's have a spinlock that's polling a status register very fast. There is some problem with this code, because sometimes this status polling says the DUT reports a reset state when it's between device switching modes (and let's assume that this occurrence puts your driver stack in a fault mode).

 So you add a printf() for the status data. But as you do, you're changing the sampling rate of the status register significantly, and with that also decrease the chance of hitting the problem. Of course there are multiple ways of debugging further. You could only printf changed values. You could add a conditional statement to begin with. You could bitmask a certain state and write it's value onto a GPIO, which could be used for o'scope/LA triggering as well. If you have a good trigger on the LA, maybe you don't even need a printf at all because you directly probe the interface bus? (or spit out status bytes on a max. speed SPI bus, instead of printf tracing them) etc.

Trying to solve problems with conditional breakpoints is actually quite slow. AFAIK the debugger will set a regular hardware breakpoint, and once that's hit will evaluate a certain C expression before it exposes the breakpoint-hit to the user. Under the hood it has to read some CPU registers or data that it's a PC-software breakpoint with long round trip times. Not ideal to debug time sensitive problems..

-----
Concerning host based printf.. that's exactly what I did. I assigned unique file IDs to all source files. I format the 16-bit file ID with a 16-bit line number to create an unique printf ID. This ID is sent to the host, which can then parse it and grab the appropriate format string from the ELF file (you can also store the strings in a debug section) or directly from source.
Sending data arguments quickly is quite easy in C++ with a template. That gives full type information at compile time of the argument chain. It only generates the minimal amount of code needed to send those arguments to the host. The host should consume the incoming data bytes in a correct way, similar to a printf, to not over or underrun the data stream (e.g. don't use %lX for a 16-bit type).
 

Offline pcprogrammer

  • Super Contributor
  • ***
  • Posts: 3700
  • Country: nl
Re: how do you debug the code?
« Reply #22 on: June 19, 2022, 07:40:27 am »
Trying to solve problems with conditional breakpoints is actually quite slow. AFAIK the debugger will set a regular hardware breakpoint, and once that's hit will evaluate a certain C expression before it exposes the breakpoint-hit to the user. Under the hood it has to read some CPU registers or data that it's a PC-software breakpoint with long round trip times. Not ideal to debug time sensitive problems..

This is certainly the case when working on an USB driver in a MCU. You cant stop it because then the host times out and it does not work anymore :(

Offline Psi

  • Super Contributor
  • ***
  • Posts: 9946
  • Country: nz
Re: how do you debug the code?
« Reply #23 on: June 19, 2022, 08:03:38 am »
You tend to find engineers fall into two groups. Those who use and trust the debugger and those who don't.

Engineers who don't use/trust the debugger very much tend to send debug messages over UART, or flash an LED, or toggle GPIOs while looking at scope/logic_analyser etc.

It's really up to personal preference and which you find work better for you. Most engineers will use both but favor one much more than the other.

The most annoying thing about using a debugger is those situations where the problem goes away as soon as you enable the debugger and try and catch it.  :scared:

When using a debugger there's quite a few traps for young players. Trying to debug with too much optimization enabled etc..
So the debugger does have its own learning curve to it. Also the debugger for every CPU type will have its own quirks and traps that you will discover.
That said, MCU debuggers have gotten a lot better than they used to be.



« Last Edit: June 19, 2022, 08:09:56 am by Psi »
Greek letter 'Psi' (not Pounds per Square Inch)
 
The following users thanked this post: tooki

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21681
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: how do you debug the code?
« Reply #24 on: June 19, 2022, 11:08:52 am »
Optimization itself is a matter of preference.  One can argue the validity or merits of the approaches, but it's largely irrelevant these days, what with embedded CPUs of various capability being essentially equivalent in price, and that price itself being minor compared to development time/cost (and project timeline / time to market).

There are two kinds; and probably they correlate some with the debugging dichotomy above?

One is to simply write what seems right, what works.  Keep it at -O0 or at worst -O1, because the compiler ""introduces bugs"" and ""breaks working code"".  And we have plenty of CPU power to deal with it, who cares.  This is more common in "it has to be right", quantitative or safety oriented programming I think.  (Related, there's also hardware approaches, like redundant or lockstep cores -- another method very wasteful of CPU resources, but in a different way, and for different purposes.)

The other is to study and understand the C language itself, in great depth, so as not to be afraid of what the optimizer can do.  The compiler can't discover your intent from what you've written, if you've written it ambiguously with respect to your purpose.  Think of the optimizer as lawyer-gaming your code, trying to find outs that generate minimal machine code (not necessarily code per se, but whatever the optimization goal is set to, usually speed or size).  Think about what you write, in every possible way, game it out yourself and figure if one of those is broken or not.  Probably, this kind of programmer will produce uglier code -- because it's more closely tailored to the ugly intricacies of the C language itself, e.g. using more specifiers like const and volatile -- and probably there's more inspection of machine code going on, so, requiring knowledge of the machine ISA, and some tweaking of statements to try and generate better output in the first place.

The first is likely to be cheaper in development, if perhaps more expensive as a recurring cost (more powerful CPUs); it's also perhaps more portable, using fewer nuances tailored to the platform it was developed on.  (Mind, the latter approach can be just as portable, depending on how one does it -- sticking to strict language features, not platform dependencies or tall stacks of #defines.)

One can also -- and should -- run unit tests, from either class, though perhaps these are more likely in the latter than former?  If it passes the test, and the test has complete coverage, then it doesn't matter how casual and idiomatic, or nuanced and cryptic, the source is.  The challenge of course is recognizing when edge cases are desired normal behavior, irrelevant side products, or program-breaking bugs.  (Often, tests get developed from whole lists of issues, accumulated over years or decades of bug fixes; good tests can be very hard won indeed!)

I don't know that there's much TDD (test driven development) in the embedded space though; it's hard, with so many platform-specific dependencies.  The few things you can, you should though.

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf