adamgreen / mri

MRI - Monitor for Remote Inspection. The gdb compatible debug monitor for Cortex-M devices.
Apache License 2.0
155 stars 57 forks source link

How to redirect stdout/stderr/stdin to gdb console #26

Closed xiaoxiang781216 closed 2 years ago

xiaoxiang781216 commented 2 years ago

README(https://github.com/adamgreen/mri/blob/master/README.md?plain=1#L14) mention that:

stdout/stderr/stdin are redirected to/from the GDB console

@adamgreen could you tell me the detailed step to setup for both(gdb and device) side? Thanks.

adamgreen commented 2 years ago

When using the semi host support, if you write to file handle number 1 then you are writing to stdout. Writes to file handle number 2 will go to stderr and reads from file number 0 come from stdin. Nothing needs to be done on the GDB side.

In the case of newlib, it already does this for us. When you call functions like printf(), several calls later down in the call stack you will see a call to the _write() sys call for file handle number 1. In the project I am currently working on, _write() is implemented like this: https://github.com/adamgreen/rugrover/blob/master/mriblue_boot/shared/newlib_retarget.c#L139 which ends up calling mriNewlib_SemihostWrite() that is nothing but a bkpt MRI_NEWLIB_SEMIHOST_WRITE instruction that will cause a transfer into MRI to satisfy the write request via GDB's file API. The file number of 1 will be passed to GDB and it does the right thing by sending the data to stdout.

Does that help?

adamgreen commented 2 years ago

When you call printf() in NuttX, where does it send the output? You want to do whatever you need to do in NuttX to get it to use the ARM semihost layer for this output as well.

adamgreen commented 2 years ago

I should note that it is something you probably want to be able to turn on and off though as semihost redirection of stdout will be a lot slower than just dumping it out a serial port or other channel.

xiaoxiang781216 commented 2 years ago

When using the semi host support, if you write to file handle number 1 then you are writing to stdout. Writes to file handle number 2 will go to stderr and reads from file number 0 come from stdin. Nothing needs to be done on the GDB side.

In the case of newlib, it already does this for us. When you call functions like printf(), several calls later down in the call stack you will see a call to the _write() sys call for file handle number 1. In the project I am currently working on, _write() is implemented like this: https://github.com/adamgreen/rugrover/blob/master/mriblue_boot/shared/newlib_retarget.c#L139 which ends up calling mriNewlib_SemihostWrite() that is nothing but a bkpt MRI_NEWLIB_SEMIHOST_WRITE instruction that will cause a transfer into MRI to satisfy the write request via GDB's file API. The file number of 1 will be passed to GDB and it does the right thing by sending the data to stdout.

Does that help?

Yes, very useful. I have three more questions:

  1. Where will gdb output for stdout(1)/stderr(2)
  2. Where will gdb get input for stdin(0)
  3. If the user doesn't input anything, does the semihost read block until user input something or return with 0 length directly

When you call printf() in NuttX, where does it send the output? You want to do whatever you need to do in NuttX to get it to use the ARM semihost layer for this output as well.

Yes, it's very easy to wrapper a special console/syslog driver on top of semihost layer. We will do that after the above questions get confirm.

I should note that it is something you probably want to be able to turn on and off though as semihost redirection of stdout will be a lot slower than just dumping it out a serial port or other channel.

It's reasonable since the trap and RSP add more overhead, but the benefit is that we can use the same physical UART port to do the debug and shell at the same time.

adamgreen commented 2 years ago
  1. Where will gdb output for stdout(1)/stderr(2)
  2. Where will gdb get input for stdin(0)
  3. If the user doesn't input anything, does the semihost read block until user input something or return with 0 length directly
  1. It goes to the GDB console. If you are running GDB from the command line, then it goes to the same console where you type in GDB commands and the results come back to you. If you are running in an IDE then I am not sure. Usually there is a GDB console window in the IDE where you can type commands manually to GDB even from the IDE. It should definitely show up there and maybe the IDE can intercept it and redirect it somewhere else if the GDB MI interface flags it as debuggee output...I don't know for sure. I rarely use IDEs.
  2. Same as the answer to 1.
  3. Yes, it is blocking and as far as I can tell there is no non-blocking solution for determining if the user has typed something in GDB while the debuggee is running. What I have done in some of my programs in the past is to have my embedded program not normally read from stdin. However I have a global variable named something like g_debug that I can set to 1 from within the debugger and then the main program loop checks this global and when not 0 it runs code which outputs a debug menu to stdout and then waits for input from stdin.

Here is some example output from a recent run of my robot which dumps to stdout the number of PID updates that have occurred every 10 seconds so that I can make sure that my PID loop is running at the expected frequency. You can see where I have typed commands and CTRL+C into the same GDB console that my program also dumps this stdout to:

GNU gdb (GNU Arm Embedded Toolchain 9-2020-q4-major) 8.3.1.20191211-git
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin10 --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from _build/RugRover.out...
Remote debugging using :3333
0x0003c4bc in Reset_Handler ()
(gdb) c
Continuing.
1000
995
1000
1000
1000
1000
^C
Program received signal SIGINT, Interrupt.
0x00009f80 in ?? ()
(gdb) q
xiaoxiang781216 commented 2 years ago

Thanks for the detailed explanation. The blocking reading behavior isn't very good, we can't build a shell which could interactive through gdb console natively.

adamgreen commented 2 years ago

Agreed. The only input you can type into GDB which will result in something being sent to MRI asynchronously is a CTRL+C which results in a halt :(

I have used that before to pop up a non-interactive shell. I detect that CTRL+C and the continue have been sent which means that I should pop up my debug menu as soon as I can because the user has connected to me with GDB. I just use the hooks set by mriSetDebuggerHooks() to notice that MRI has been entered/left and use that to know when I should activate the blocking debug menu.

xiaoxiang781216 commented 2 years ago

We will try to support shell with MRI on NuttX, let's see how we can improve the input experience and report back to you.

xiaoxiang781216 commented 2 years ago

Got answer, let's close now.

adamgreen commented 2 years ago

One other caveat I will mention is that if your program does direct stdout to GDB via MRI's semi-hosting support, the program will hang the first time it tries to write something to stdout if GDB isn't attached since it will wait for the ack back from GDB. There are hacks that you can do for a particular platform port to work around this a bit. Let me know if you want to do know more. I can point you to some hacks that I have done in the past.

xiaoxiang781216 commented 2 years ago

Sure, could you point me to the related code? We can try how to integrate that piece of code to NuttX.

adamgreen commented 2 years ago

An overview of a hack that I did recently and others in the past have been similar:

That particular mriblue_boot implementation puts the outbound packets in a queue and at some point it will fill up and then the code will block. There are hacks I could do in that particular implementation to work around that but I don't have a need for that at this stage in my robot project since I always have GDB attached and I want to lose as little printf() text as possible.

xiaoxiang781216 commented 2 years ago

An overview of a hack that I did recently and others in the past have been similar:

I think this is a general enhancement and should be part of the mainline.

It is right there in the packet (but hexadized so that it now takes 2 characters per byte).

Yes, 'O' need convert the binary to hex which lose the performance. But, "Fwrite" packet is more slower than 'O':

  1. Need three trip("Fwrite"->"m"->"Frr,ee,cc")
  2. "m" also need convert the binary to hex