Closed xiaoxiang781216 closed 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?
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.
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.
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:
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.
- Where will gdb output for stdout(1)/stderr(2)
- Where will gdb get input for stdin(0)
- If the user doesn't input anything, does the semihost read block until user input something or return with 0 length directly
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
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.
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.
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.
Got answer, let's close now.
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.
Sure, could you point me to the related code? We can try how to integrate that piece of code to NuttX.
An overview of a hack that I did recently and others in the past have been similar:
g_fakeAckCount
is used to account for the fact that MRI is going to want to wait for the '+' ack byte back from GDB for this 'O' packet so just pretend that you got it from GDB when MRI fetches the next character.g_ignoreAckCount
takes care of the fact that if GDB is connected then it is going to respond with a '+' ack at some point for an ack that we already faked. This global is used to know that these ack bytes should just be discarded when they are received from GDB.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.
An overview of a hack that I did recently and others in the past have been similar:
- Provide my own implementation of the semihost write function which detects that a write to stdout is being attempted and send an 'O' packet with the output to GDB instead of a "Fwrite" packet. The 'O' packet is used because it doesn't need to wait around for GDB to send 'm' packets to read out the buffer contents.
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':
README(https://github.com/adamgreen/mri/blob/master/README.md?plain=1#L14) mention that:
@adamgreen could you tell me the detailed step to setup for both(gdb and device) side? Thanks.