The execution engine works in discrete[a] operations, hence uops. The microcode is a sequencer that tears apart ISA instructions into those uops.
[a]: I'm considering fused operations (like CMP+Jcc) as single "operations" for simplicity.
It you don't rush and focus on making progress, you will find it eventually. There is no wizardry involved, skills will help you find the solution faster, but that's your ability to focus and keep your calm that will get to it in the first place.
If you are at work, ignore your boss trying to pressure you (that's not necessary easy), your boss only has the "right" to tell you if you should work or the problem or not, if you are asked to work on the problem, do it as if you have your entire life to do it. With experience, you can make estimates that can help decide if the problem is worth trying to solve or not, but with the correct mindset, one doesn't need experience to solve the problem itself, instead one builds experience by solving the problem.
Nevertheless, I recommend learning the details of computer architecture by learning some assembly programming. Higher level languages are an abstraction meant to shield the developer from the details of the H/W but it remains useful to understand the details, particularly if the abstraction is broken.
Kind of like experience gained as a plumber working with dirty clogged real pipes in a basement, vs doing exercises in plumbing school with new pipes and theory about pipe sizes, gradients etc. In other words, school can only give part of the picture.
And great parties as well.
It's a waste of a student's time. Deep debugging isn't just one set of skills; each problem is different. It's usually very time-consuming, and you will need to learn new skills and tools.
That is: you have to encounter a problem that is a blocker, so that your motivation is that you have to solve the problem.
My experience was a long time ago: I had linked a library compiled with Borland C with code compiled with Microsoft C. It wouldn't work. I wss only using one function from the Borland library; that's where the error was occurring. It turned out that the Microsoft compiler required the callee to restore the flag register; the Borland compiler required the caller to do that. Therefore the carry bit wasn't being restored correctly, causing the bug. Took several days to figure out.
Spend some time learning gdb and other classic cli tools.
Don't be afraid of reading books!
Sometimes that can be turning an intermittent problem into something I can reproduce consistently, and sometimes it can be constructing new test cases and seeing if their results match my hypothesis.
It helps to either have a good memory, or to keep notes so you know what you’ve already explored and tested, and it’s something you get better at with practice. I spent several years on third line product support working out what was going wrong for customers, producing hot fixes, and working with devs to turn those into proper fixes in the next version.
That's how I got there at least...