i think you have a serious misunderstanding on how debugging a system using an ICE or TRACe probe works.
Just like you trigger events and send out a debug report the probe is set up to trigger on events and take a snapshot of anything you'd like to see.
The difference is you don't use the system processor to produce the report. The probe does that.
The probe can get to areas where your printf solution cannot go and it can do it without corrupting the processor state. You jump out and start collecting bits and bytes by copying them or starting to send them out using printf. The time it takes to collect and send out the contents , the data may have changed so you never see what is there at the moment of the trigger. This is especially true when you are trying to bring up the HAL or BSP.
i'm going to print out the content of a circular hardware buffer. while you are print'effing the contents of the buffer are still changing. so all you get is useless data.
Another issue is that , If your report produces nothing of interest, you are going through another code, compile, load, run cycle to add other fields that may or may not yield something.
With an ICE and TRACE : if the event fires you have the entire processor state and the stuff of interest in one clockcycle . "Big" processors have trace assist where they can even record many cycles before the trap event. you can replay
Another thing you can do is to call functions of your system.
event occurs -> trap and halt , examine anything you want using the trace without state change (since it is done using a backdoor and does not require processor resources). alter content of data / registers through the debugger then move the execution pointer. You can let the trapped code continue until the exitpoint , halt it again there , set up new data and call the function again , deviating form the normal flow.
main :
call some_function (123)
*breakpoint if trapped=true
call some_other_function
...
some_function (int bleh) :
internal variables x,y,z
do some stuff here that messes up sometimes
*trap if condition is met. example y>9
end some_function
If the trap fires in "somefunction" the processor state is saved together with what you want (this is set up in the ice, no need to write code)
You can examine x,y and z.
You find out y is not what it should be. set y to expected value and continue to verify it is indeed the problem.
Or, since the trap fired, continue. now the breakpoint will fire (the breakpoint is conditional : only if the trap fired will the program stop there.
You can now call the function again, manually.
You can interactively just type "somefunction(17)" and the processor will execute that function again. all without the need of recompiling or spending time implementing i/o functionality to fish for data or to invoke functions.
you also do not need to do the endless fix a bug compile, find the next one , compile again.
you can fix things in place and continue the run. Once you hit the point where you can't fix in place then you stop , go fix the source for everything you found.
Emulators let you modify code.
you can create an entirely new some_function, load it in ram and alter the calls so the new some_function is used instead of the old one. no need to recompile the entire program. just the function itself.
it's damn handy to be able, not to just look at things, but interactively modify and execute things while in trap
it's not always a bug per se. it may just be an unexpected or unhandled state . your code is correct but something happens for which you have no logic. something that should not happen, but does. first you need to find what happened, then why did it happen ? your printf debug style is useless for such cases since you don't know the "what" so you can't code a report generator for it. The ICE is a "catch all".
There's a reason companies pay big bucks for those tools. When i was still doing harddisks every bench and desk had one of those probes. Lauterbach , Greenhills , American Arium.
They key strength is go anywhere, anytime, look at anything and do anything without needing to write debugging code or recompilation / relaunch. fix in place and interactively execute.