feat(guest): replace musl with picolibc#831
feat(guest): replace musl with picolibc#831andreiltd wants to merge 6 commits intohyperlight-dev:mainfrom
Conversation
617d834 to
3f8ffcf
Compare
eaf1d54 to
b18a374
Compare
|
will help with #282 |
499c59d to
2ae0a3a
Compare
2ae0a3a to
d3826af
Compare
d2043ac to
2bd1ff4
Compare
65634a7 to
980cb20
Compare
c24ce45 to
d9fb9e5
Compare
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
e4caabe to
7b99307
Compare
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
7b99307 to
fb1549f
Compare
src/hyperlight_guest_bin/c/clock.c
Outdated
There was a problem hiding this comment.
did we write this file? is there attribution that is needed?
There was a problem hiding this comment.
If this is really necessary, I think @jprendes just wrote a Rust implementation for this in hyperlight-js. maybe we could use it?
There was a problem hiding this comment.
Yes, those are ours. We cannot use the implementation from hyperlight-js directly, it serves a bit different purpose. That being said I rewrote picolibc stubs to rust. The tradeoff is that some of the types and defines that you normally get from libc headers have to be redifined in Rust with compatible ABI. I think that is OK -- they have to be backwards compatible and I don't see them change anytime soon.
src/hyperlight_guest_bin/c/clock.c
Outdated
| (void)__tz; | ||
|
|
||
| _current_time(current_time); | ||
| tv->tv_sec = current_time[0]; |
There was a problem hiding this comment.
do we need to check for null here?
There was a problem hiding this comment.
It's now checked in Rust implementation.
src/hyperlight_guest_bin/c/clock.c
Outdated
| case CLOCK_REALTIME: | ||
| case CLOCK_MONOTONIC: | ||
| _current_time(current_time); | ||
| tp->tv_sec = current_time[0]; |
There was a problem hiding this comment.
It's now checked in Rust implementation.
| Some(vec![ParameterValue::ULong(count as u64)]), | ||
| ReturnType::VecBytes, | ||
| ) { | ||
| Ok(bytes) => { | ||
| let n = bytes.len(); | ||
| unsafe { | ||
| core::ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, n); |
There was a problem hiding this comment.
should not rely on the host provided length. nonoverlapping could write past the buffer lenghth. We are in the guest here so it would just cause corruption but might be tough to track down I think
| return -1; | ||
| } | ||
|
|
||
| let slice = unsafe { core::slice::from_raw_parts(buf as *const u8, count) }; |
There was a problem hiding this comment.
do we need a null check on the buf here? https://doc.rust-lang.org/core/slice/fn.from_raw_parts.html#safety
data must be non-null and aligned even for zero-length slices
There was a problem hiding this comment.
It's now checked in Rust implementation.
| obstacle to adoption, that text has been removed. | ||
| Note: The picolibc submodule uses sparse checkout to exclude | ||
| GPL/AGPL-licensed test and script files that are not needed for | ||
| building. Only BSD/MIT/permissive-licensed source files are included. |
There was a problem hiding this comment.
Just wanted to surface this. We should include it in the PR description I think
There was a problem hiding this comment.
Where is the sparsity pattern that makes this actually happen?
There was a problem hiding this comment.
dblnz
left a comment
There was a problem hiding this comment.
Great work, Tomasz!! This is one hell of a diff and I like it 😆
src/hyperlight_guest_bin/c/clock.c
Outdated
There was a problem hiding this comment.
If this is really necessary, I think @jprendes just wrote a Rust implementation for this in hyperlight-js. maybe we could use it?
src/hyperlight_guest_bin/c/clock.c
Outdated
|
|
||
| extern void _current_time(uint64_t *ts); | ||
|
|
||
| int gettimeofday(struct timeval *__restrict tv, void *__restrict __tz) { |
There was a problem hiding this comment.
Thought: If we were running bindgen on picolib headers, then we could easily implement this one in rust instead of C. IIUC, the main issue is the definition of timeval, clockid_t, and timespec in Rust, that we don't want to get wrong.
There was a problem hiding this comment.
This file is no longer in the diff.
There was a problem hiding this comment.
If we were running bindgen on picolib headers, then we could easily implement this one in rust
That would be correct yes, but I would rather much have a single c file defining the interface or redefine types in Rust with correct layout. It's hard to justify running the whole bindgen machinery for few u64.
| ReturnType::VecBytes, | ||
| ) { | ||
| Ok(bytes) => { | ||
| let n = bytes.len(); |
There was a problem hiding this comment.
maybe check that bytes.lenth() <= count?
There was a problem hiding this comment.
Yeah, I think the timing is a bit unfortunate, could you check if your comments are resolved in the newest diff?
| ) { | ||
| Ok(bytes) => { | ||
| let n = bytes.len(); | ||
| unsafe { |
| } | ||
|
|
||
| let slice = unsafe { core::slice::from_raw_parts(buf as *const u8, count) }; | ||
| let s = core::str::from_utf8(slice).unwrap_or("<invalid utf8>"); |
There was a problem hiding this comment.
I think I would like to insist on the from_utf8_lossy, as mentioned we are allocationg anyway when doing s.to_string()
|
Is the licensing approval finally done for this, making it ready for review/merge? |
|
|
||
| #define GUEST_SCRATCH_SIZE (0x40000) // default scratch size | ||
| #define MAX_BUFFER_SIZE (1024) | ||
|
|
||
| #define printf_f(fmt, ...) \ |
There was a problem hiding this comment.
Does it make sense to see if we can either build picolibc in a configuration without stdio buffers, or to automatically do a flush on the guest-function-call-complete path?
I feel like the buffered-I/O-by-default regime will be very confusing to users---where, for example, if you are doing the "call a function and then restore the sandbox" pattern might often not see any output at all.
There was a problem hiding this comment.
Yeah, I think we can disable posix console feature: https://github.com/picolibc/picolibc/blob/main/doc/build.md#stdio-options
Fuzzy on the details but IIRC that will change to char by char sending to host which I thought was insane.
There was a problem hiding this comment.
Yeah, that makes sense... char-by-char is definitely unfortunate. Flush-on-guest-function-return would be nice if we could coalesce the flush exit and the function-return exit, but I'm not sure how easy that is?
|
|
||
| #[unsafe(no_mangle)] | ||
| pub extern "C" fn _current_time(ts: *mut u64) -> c_int { | ||
| let bytes = call_host_function::<Vec<u8>>("CurrentTime", Some(vec![]), ReturnType::VecBytes) |
There was a problem hiding this comment.
Similarly, I think I might like to see this fail at least by default? As with my comments on #1173, I'm against exposing a clock, especially a wall clock, to the guest by default.
There was a problem hiding this comment.
This is very common dependency, for example spidermonkey crashes immediately if we don't provide some time interface. Do we really want to error or at least return an unix epoch?
There was a problem hiding this comment.
Interesting... I didn't realise so many things were using wall-clock time. Do you know what we were doing for spidermonkey before? I don't think we did actually have the real time?
I wouldn't be against having it always be the epoch personally (at least by default), but I'll let others chime in here as well.
There was a problem hiding this comment.
at some point we had a fallback where we took some epoch (2000?) and added 1s or 1ms for each call
| static CURRENT_TIME: AtomicU64 = AtomicU64::new(0); | ||
|
|
||
| #[unsafe(no_mangle)] | ||
| pub extern "C" fn read(fd: c_int, buf: *mut c_void, count: usize) -> isize { |
There was a problem hiding this comment.
Where are the fds referenced in these functions allocated? I don't see an open, so are these always either 0/1/2 for stdin/out/err? I think it might make sense to make the behaviour a bit more limited (maybe reads from fd0 always return eof; writes to fd1 are HostPrint, and writes to fd2 are debug_print, or something like that?)
There was a problem hiding this comment.
That's almost what you have here, right? except that:
- fd2 also goes to
HostPrintinstead ofdebug_print(I didn't knowdebug_printwas a thing!). - fd0 first tries
HostRead, if that's not present returns-1and setserrno = EIO(instead of EOF, which would be returning0and noerrno) - any other fd is
EBADF
So, do you suggest these changes?
- fd2 to use
debug_printinstead ofHostPrint - fd0 to return
0(and untouchederrno) in case of noHostRead
There was a problem hiding this comment.
I don't care much about the fd2---it was just the first thing that sprang to mind.
Unless we have a compelling use case, I think I would prefer for read from fd0 to unconditionally be eof, without probing for a host function which seems a bit surprising and magical. If we ever have C code that needs to be able to read() from stdin, we could add some hyperlight_guest_bin API to let a little Rust wrapper code install its own read function.
There was a problem hiding this comment.
I'm happy to go with that logic. @jprendes any objections?
There was a problem hiding this comment.
you can call fflush(NULL) and that flushes all open FDs.
Can we leave buffering and add that on VM exit (or similar) in an upcoming PR?
There was a problem hiding this comment.
also I think our current _putchar impl can go away, right?
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
dblnz
left a comment
There was a problem hiding this comment.
Great work, Tomasz!
I have nothing else to add.
There was a problem hiding this comment.
This looks great! Thanks for adding this description!
This patch removes custom musl implementation used by the guest and replace it with picolibc v1.8.11.
Note: The picolibc submodule uses sparse checkout to exclude GPL/AGPL-licensed test and script files that are not needed for building. Only BSD/MIT/permissive-licensed source files are included.