-
Notifications
You must be signed in to change notification settings - Fork 3k
Fix: Return the correct std I/O device handle for Microlib in retarget code #12758
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: Return the correct std I/O device handle for Microlib in retarget code #12758
Conversation
@hugueskamba, thank you for your changes. |
You say what the change is in the description, but not why you're doing it. What is microlib passing? Is it documented? |
platform/source/mbed_retarget.cpp
Outdated
@@ -756,11 +756,13 @@ extern "C" int PREFIX(_write)(FILEHANDLE fh, const unsigned char *buffer, unsign | |||
extern "C" ssize_t write(int fildes, const void *buf, size_t length) | |||
{ | |||
#if MBED_CONF_PLATFORM_STDIO_MINIMAL_CONSOLE_ONLY | |||
// Microlib does not pass STDOUT_FILENO or STDERR_FILENO when _sys_write is called. | |||
#if !defined(__MICROLIB) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is about microlib passing weird things to _sys_write, then the logical fix is to put the workaround in _sys_write. Make it call write
with STDOUT_FILENO when passed whatever the bad thing is. You don't want to disable error checking for other users calling write
.
Does microlib support files in general?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
microlib first opens :tt
for which our open()
in mbed_retarget
returns 0 (stdin):
mbed-os/platform/source/mbed_retarget.cpp
Lines 555 to 563 in 83c1b34
extern "C" FILEHANDLE PREFIX(_open)(const char *name, int openflags) | |
{ | |
#if defined(__MICROLIB) | |
// Before version 5.03, we were using a patched version of microlib with proper names | |
// This is the workaround that the microlib author suggested us | |
static int n = 0; | |
if (std::strcmp(name, ":tt") == 0 && n < 3) { | |
return n++; | |
} |
The open()
code suggests :tt
should (?) be opened three times (once for in, out, err each) but debugger shows it's only opened once. This might be related to why write()
, which is run after open()
, gets called with 0
file descriptor instead of 1
(stdout)? @kjbracey-arm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...then the logical fix is to put the workaround in _sys_write.
The current change seemed more logical to me. If the postman is scared of dogs and you have a dog to protect your home, you keep your dog away from the door when the postman arrives. You don't ask for a new postman...🌝.
You don't want to disable error checking for other users calling write.
How will other users be affected since the check is present for everything except Microlib (#if !defined(__MICROLIB)
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
write
is a public function. People can use it for their own files directly (open
/read
/close
), regardless of any issues in microlib. That's what I meant by "other users" - other users of write
in a system using microlib.
In the minimal console case, you do want to trap someone doing something weird - given the above call sequence you want to give an error when given the "-1" handle that open
gave you when trying to open a file, not send the stuff that should have gone to file to the console. That check shouldn't be removed because of an issue in one user.
At a minimum you'd want to at least ensure it was a valid TTY handle of some sort - maybe accept <= STDERR_FILENO
if the numbers from microlib are fuzzy.
Now, LDong's description looks on the point - something weird is happening with the way microlib opens our streams. Is it that microlib doesn't actually open 3 in sequence like the code assumes? Is it opening on-demand - first call to each of stdin/stdout/stderr?
Does it at least open twice, so we could look at the mode and return either STDIN_FILENO
or STDOUT_FILENO
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At a minimum you'd want to at least ensure it was a valid TTY handle of some sort - maybe accept
<= STDERR_FILENO
if the numbers from microlib are fuzzy.
This is what mbed_file_handle()
does (when not using minimal-console):
mbed-os/platform/source/mbed_retarget.cpp
Lines 371 to 386 in 83c1b34
/* Deal with the fact C library may not _open descriptors 0, 1, 2 - auto bind */ | |
FileHandle *mbed_file_handle(int fd) | |
{ | |
#if !MBED_CONF_PLATFORM_STDIO_MINIMAL_CONSOLE_ONLY | |
if (fd >= RETARGET_OPEN_MAX) { | |
return NULL; | |
} | |
FileHandle *fh = filehandles[fd]; | |
if (fh == FILE_HANDLE_RESERVED && fd < 3) { | |
filehandles[fd] = fh = get_console(fd); | |
} | |
return fh; | |
#else | |
return nullptr; | |
#endif // !MBED_CONF_PLATFORM_STDIO_MINIMAL_CONSOLE_ONLY | |
} |
when the file descriptor is < 3, it just uses
get_console()
which returns the default console in most cases. (Also the comment there suggests descriptors 0,1,2 indeed may not be open by the C lib.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple of paths into all this code. You are allowed to have an mbed_override_console
that sends stdin, stdout and stderr to 3 separate FileHandle
devices - that's probably the only place we really care. If mbed_override_console
isn't added by the app or target, then all 3 do go to the same device anyway.
Having a restriction that mbed_override_console
doesn't allow different streams for microlib because it doesn't properly distinguish wouldn't be a huge deal.
But as I said in my previous comment - maybe it's only not distinguishing because we're not trying. Those :tt
previously would have been us knowing what name it was going to give us regardless. But maybe it's now actually obeying them, and we could change them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_sys_open()
is called once with name: ":tt"
and openflags: 4 (O_RDONLY | O_NONBLOCK)
. Does it make sense to return STDOUT_FILENO
with these values...🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4 is OPEN_W
for the ARM C library (see ARMCompiler6.13/include/rt_sys.h
):
/*
* Open a file. May return -1 if the file failed to open.
*/
extern FILEHANDLE _sys_open(const char * /*name*/, int /*openmode*/);
/*
* openmode is a bitmap, whose bits are given below. They
* correspond directly to the ANSI mode specification.
*/
#define OPEN_R 0
#define OPEN_W 4
#define OPEN_A 8
#define OPEN_B 1
#define OPEN_PLUS 2
/*
* These names should be special strings which will be recognised
* by _sys_open and will cause it to return the standard I/O
* handles, instead of opening a real file.
*/
extern const char __stdin_name[];
extern const char __stdout_name[];
extern const char __stderr_name[];
That may be because your app is only using stdout
though.
What if you use stdin, or stderr, either before or after?
The description has been updated. Microlib passes the value |
Can you probe this a little more. What does it do if you use both stdin and stdout? Can you trace the calls to Hang on. Do we know that the |
Yes, still For this bit, my understanding is that the old microlib has the patch to use mbed-os/platform/source/mbed_retarget.cpp Lines 100 to 106 in 978db96
(See which way round the version macro is applied.) The above is from 5.15. The version macro was dropped during the removal ARMC5 support, so we lost the info on master... |
It's worth rechecking though - that's really old code. We were working from the ARMC5 maintenance branch, which probably wasn't getting any functional changes like that. It's quite conceivable that the microlib shipped with ARM Compiler 6 may have had this improved. (My reading is that early Mbed was using a custom microlib build, but later switched to standard ARM C 5 which no longer had been tweaked). I can't find anything specifically in the ARM C 6 manual saying that |
@kjbracey-arm Ah finally got your point. @hugueskamba Maybe we could try to drop the '#if' part of mbed-os/platform/source/mbed_retarget.cpp Lines 100 to 106 in 978db96
to see if microlib actually maps the name to /stdout , etc. which could solve the problem altogether if it works?
|
@LDong-Arm your suggestion does not work as |
@kjbracey-arm
|
Looks sensible |
That stuff is about semihosting specifically, but hopefully microlib will follow the same pattern. Again, you should be able to experiment and check what it does. |
83c1b34
to
04b333e
Compare
04b333e
to
4ccc802
Compare
@kjbracey-arm @LDong-Arm , |
4ccc802
to
e7b05d5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks for the fix
…t code Return the correct filehandle based on the mode requested. The mode is used as the pathname is always the default one (":tt") for Microlib. The previous implementation relied on three successive calls to open the std I/O device handles, this was not the case.
e7b05d5
to
eca09b7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good now.
Only remaining concern is that I wonder why microlib doesn't do the call to mbed_file_handle
that the other code does.
Digging through the history, that dates back to the initial import - there were 3 calls to init_serial()
there which later got transformed into get_fhc(fileno)
then finally mbed_file_handle(fileno)
.
That was never there for microlib.
I think it doesn't matter any more - I think the calls could be removed, as the init happens the first time mbed_file_handle
calls get_console
- any call referencing the console can do it.
I think I left them in by caution, assuming that the C library was calling _sys_open
on start up, so I thought it might be doing something to ensure pre-main init, but in fact the C libraries call _sys_open
on first use themselves anyway - that call may never happen. The console might only ever be used via, eg, write(STDERR_FILENO)
in a crash. (And we've had to take care to make sure that init can happen in that context if necessary. It's been problematic).
So no change needed for this PR - there is a historical inconsistency, but I don't think it causes a problem.
This is only the case when we enable minimal-console (no fancy file system support) - not using |
CI started |
Test run: SUCCESSSummary: 6 of 6 test jobs passed |
Summary of changes
Return the correct filehandle based on the mode requested. The mode is used
as the pathname is always the default one (":tt") for Microlib. The
previous implementation relied on three successive calls to open the std
I/O device handles, this was not the case.
Impact of changes
It is now possible to print to the console when
"platform.stdio-minimal-console-only"
is set totrue
and"target.c_lib"
is set to"small"
when the binary is built with the ARM toolchain.Migration actions required
N/A
Documentation
N/A
Pull request type
Test results
Reviewers
@evedon @kjbracey-arm @LDong-Arm