Probably the most important tip you can use is DebugBreak.
Put DebugBreak() in your code, and when it executes it's like hitting a break point.
The real nice thing is that you can then put conditionals on it that might ber hard to set on a regular breakpoint. Learn to use this!
For example, your program is crashing when it digests a certain data file. You discover that it crashes in a certain function, but only after it's called a million times+.
You also have figured out that it is crashing because a certain variable call it x has the value 1001, but x is supposed to be between 0 and 1000. So instead of hoping to luckily catch the place where x becomes to big, you find every place that x changes. Right after that you put the statement:
if(x>1000) DebugBreak();
Yes you can do this with conditional breakpoints, but I've seen a program that takes 1 second to execute slow down to 15 minutes with three coniditional breakpoints, but execute in 1.5 seconds with the DebugBreak.
Having said that here are a couple of useful suggestions. Mathematically prove to yourself that the reason you think a bug is happening accounts for the actual bug happening at least part of the time ( not likely to have two bugs create the same problem, but it happens ). I've seen some of the most stupid fixes put in place because people "feel" that's the reson for the bug. Make sure your logic is as sound as any proof in a geometry class.
The second suggestion if you put in an experimental fix, and it doesn't do anything. Take it out.