Have you ever wanted MATLAB to pause in debug mode when a certain condition is met? Perhaps when a specific problem-file out of a directory is read or on an iteration of a for-loop that yields an unexpected result? Perhaps that for-loop has to iterate a few dozen or hundred times before the problem iteration occurs and you don't want to walk through step by step in debug mode?
In this post, we're going to survey a few different ways to handle this.
I was talking with a MATLAB user recently about the merits of the keyboard function of which I'm not a big fan. Keyboard pulls you into debug mode as soon as it's encountered. They claimed to like it for difficult debugging when certain criteria are met.
Here's a simple example scenario. CM is the cameraman image:
This is a simple algorithm to show how many pixels are brighter than each pixel value in the uint8 range of [0 255].
xg = zeros(1, 256); for ii = 0:255 xg(ii+1) = sum(CM(:)>ii); end plot(0:255, xg) ylabel('Pixels Greater Than X') axis tight
Somewhere around iteration 125 appears to be giving me problems; I'd expect the curve to be positive through to 255.
I want to see what's happening on iteration 125 and the subsequent ones. One way would be to put a break point on the calculation line, hit run, and then hit the continue button 125 times hoping I don't get in a rhythm and accidentally skip it. This has some obvious shortcomings in that it doesn't scale and is time consuming.
The approach with keyboard would look like this:
xg = zeros(1, 256); for ii = 0:255 if ii == 125 keyboard end end
Now on the 125th iteration, we enter the if-statement and can step in debug mode into the problem area. This is pretty straight-forward and is a safe, decent solution. However, it requires changing the code in order to debug it and will require changing it again once we've fixed the bug.
These breakpoints can be set from the breakpoints menu on the editor tab or with dbstop directly. Here's the same code encapsulated in a function called badForLoop.
function badForLoop(CM) xg = zeros(1, 256); for ii = 0:255 xg(ii+1) = sum(CM(:)>ii); end plot(0:255, xg) ylabel('Pixels Greater Than X') axis tight end
To set it from the breakpoints menu, select "Set Condition" while the cursor is on the line you'd like to stop on.
Then enter your condition; any valid MATLAB code will work here.
To set the same breakpoint programmatically:
dbstop in badForLoop at 4 if (ii==125)
If you think you have a fix, you can then disable the breakpoint without clearing it by either clicking on the yellow breakpoint itself (an X will appear over it indicating it's disabled). If the problem is resolved, clicking the breakpoint again will clear it or you can reenable it by right clicking. This can be done from the menu as well and clearing can be done with dbclear.
The analogous workflow with keyboard would be to highlight the selection and comment it out to "disable" and then delete to clear.
The fix for this toy problem was that the image was stored as an int8 with a range [-128 127] rather than a uint8 with range [0 255].
Additionally, dbstop has some predefined conditions to stop on including on errors, warnings, or if nans or infs are encountered. If you haven't discovered dbstop if error yet, I strongly suggest giving it a try. When an error occurs, it stops and you can view the state of the world as it was when the error occurred.
Do you have any debugging war stories where one of these tricks could've helped? What about other uses for the keyboard command? Let us know here.
Get the MATLAB code
Published with MATLAB® R2017a
Comments are closed.
6 CommentsOldest to Newest
In the past it has seemed to me that using a conditional dbstop can exact an extreme amount of overhead relative to the unaltered code. (I’ve not found this using the dbstop if error), but when setting a custom condition on a loop that runs many, many cycles can slow down the execution to the point where it wasn’t feasible to use it. Is there some interference between dbstop conditions and the JIT compiler, perhaps? In those cases, I’ve found that the code runs much, much faster by using an approach that looks like your keyboard approach. Am I missing something?
Here’s an example:
N = 10e5;
testCond = 0.99*N;
for i = 1:N
% Just create something to do so it doesn't optimize away
j = i+1;
for i = 1:N
% Just create something to do so it doesn't optimize away
j = i+1;
I put a conditional breakpoint on the line that says: j = i+1; in the first case, testing for j==testCond.
The break statements are so that I can time it without actually tripping the debug condition.
The times for the two cases were:
Elapsed time is 34.506428 seconds.
Elapsed time is 0.004920 seconds.
Is there an approach to using dbstop that doesn’t pay this penalty?
Slight addition to that earlier comment. I pay an even worse penalty if the breakpoint is disabled, but not if it is deleted.
Elapsed time is 34.412000 seconds.
Elapsed time is 0.004994 seconds.
Elapsed time is 51.132263 seconds.
Elapsed time is 0.004941 seconds.
Elapsed time is 0.004422 seconds.
Elapsed time is 0.004628 seconds.
BTW: This is all using R2017a.
I’ll admit to using the keyboard command. Sometimes it’s useful to define a boolean DEBUG variable to perform several analyses in a piece of code that are only needed for debugging purposes. For instance, one may want to perform intermediate analyses and generate plots to help understand how to tune an algorithm. In that case I’ll wrap the debug code in “if DEBUG” statements and include a “keyboard” call at the end of the last one.
I often write Matlab program that include multiple branches that I wish to debug in run-time. I am often frustrated by several limitations of the dbstop mechanism:
I can manually place the breakpoints in the relevant code locations but I need to do it again and again for each Matlab session because breakpoints (like bookmarks) are not persisted across Matlab sessions. Since code-folding is persisted I see no reason why breakpoints/bookmarks should not be. In fact, I think that persisting breakpoints (and to a lesser extent bookmarks) would be more useful than persisted code-folding.
To avoid the need to remember to manually set breakpoints in each Matlab session, I often include dbstop commands in my code. For example:
try doSomething(); catch err dbstop myProgram 123 % line 123 is the next line number in myProgram.m disp(err) end
The problem with this is that I need to modify the m-file name and line number whenever I make any change to the file. This is ok for small m-files but entirely unmanageable for large complex files (which is where such constructs are most needed).
We can use dbstack to extract the current line number and then use the functional interface of dbstop, for example:
s = dbstack; dbstop(mfilename, num2str(s(1).line+1)); but this looks a bit convoluted. Ages ago BASIC had the ability to specify “Next” as the target line, and it would be very useful to have something similar in Matlab:
dbstop next %or: dbstop('next')This looks much cleaner and more readable, don’t you agree?
We cannot modify the code (or strictly speaking, the m-files that exist on the current dbstack) when we’re stopped at a breakpoint. It would be very useful to lift this limitation so that if we see a bug as a result of the breakpoint debugging, we could fix the code, have Matlab recompile the code, and enable a dbcont from that point onward. Most major debuggers/IDEs in recent decades allow this… It is very frustrating to be forced to restart a long Matlab program run, just to be able to retest the program after a small fix.
@Dan: I was not aware of the performance loss but would guess it’s because they’re EVALing the condition on each line. I’ve reported it to development.
@Eric: That’s a fair use case. I usually use sections for this and cherry pick which sections to run based on what I need.
@Yair: dbstop nextline would be very helpful; I reported this to development along with both of our votes for persistent bookmarks. I’ve rarely used bookmarks because of this limitation.
It is possible, with a couple extra steps, to make breakpoints persist across sessions.
b = dbstatus; save(fullfile(prefdir, 'breakpoints.mat'), 'b');
In the next session
s = load(fullfile(prefdir, 'breakpoints.mat')); dbstop(s.b);
These could then be added to startup.m and finish.m. Of course, it would make sense to have this as a preference.
After reading this blog post, I decided to simplify my life somewhat and created a tiny function on the file exchange (https://www.mathworks.com/matlabcentral/fileexchange/62969-dbstopif-condition-) that acts as a dbstop if the argument passed in is true. It’s not elegant, but it let’s me avoid the conditional breakpoint penalty that I mentioned above, and doesn’t require regular revision of the code.
Hope it helps,