Skip to content

feat(guest): replace musl with picolibc#831

Open
andreiltd wants to merge 2 commits intohyperlight-dev:mainfrom
andreiltd:libc-takeover
Open

feat(guest): replace musl with picolibc#831
andreiltd wants to merge 2 commits intohyperlight-dev:mainfrom
andreiltd:libc-takeover

Conversation

@andreiltd
Copy link
Member

No description provided.

@andreiltd andreiltd marked this pull request as draft August 28, 2025 11:48
@andreiltd andreiltd added the kind/enhancement For PRs adding features, improving functionality, docs, tests, etc. label Aug 28, 2025
@andreiltd andreiltd force-pushed the libc-takeover branch 6 times, most recently from eaf1d54 to b18a374 Compare August 28, 2025 13:44
Copy link
Contributor

@jprendes jprendes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love this PR!

)
.unwrap_or_default();

let n = bytes.len();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to assert n <= count?
A wrongly implemented HostRead could cause a buffer overflow

}

let slice = unsafe { core::slice::from_raw_parts(buf as *const u8, count) };
let s = core::str::from_utf8(slice).unwrap_or("<invalid utf8>");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: what do you think of using String::from_utf8_lossy instead? we are doing s.to_string() anyway.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! As long as from_utf8_lossy doesn't allocate if string is valid utf-8 which I believe is the case?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It returns a Cow, so, that's correct, but you will need to allocate a string to send it to the host function anyway... so... ¯\_(ツ)_/¯

[features]
default = ["libc", "printf"]
libc = [] # compile musl libc
printf = [ "libc" ] # compile printf
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opinion: I would just put everything under the same libc feature

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I guess any code from libc if not referenced will be DCE anyway 🤷

pub extern "C" fn write(fd: c_int, buf: *const c_void, count: usize) -> isize {
// Only stdout (fd=1) and stderr (fd=2)
if fd != 1 && fd != 2 {
return count as isize;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: shuoldn't this return -1, or some other indication of error? and set errno?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I think that in order to support this properly we would need to generate the bindings to libc headers and use that to reference errno, EBADF etc. But then, it might be better to just have picolibc-sys crate. Anyway I agree we should report an error here somehow.

pub extern "C" fn read(fd: c_int, buf: *mut c_void, count: usize) -> isize {
// Only stdin (fd=0) and only if HostRead is defined
if fd != 0 || !CAPS.host_read() {
return 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: should the case of fd != 0 return -1 and set errno instead?

@jsturtevant
Copy link
Contributor

will help with #282

@andreiltd andreiltd force-pushed the libc-takeover branch 5 times, most recently from 9f5c6c1 to a59c7ce Compare September 9, 2025 12:19
@raftario
Copy link

Is this still on the table ?

@jsturtevant
Copy link
Contributor

Yes but need approval on licensing before being able to move forward: cncf/foundation#1117

@andreiltd andreiltd force-pushed the libc-takeover branch 2 times, most recently from 65634a7 to 980cb20 Compare February 25, 2026 10:31
@@ -1,2 +1,3 @@
[build]
target = "x86_64-unknown-none"
rustflags = ["-Zdirect-access-external-data"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this come from?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC rustc generated unsupported reallocations without this flag

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nightly flag, right?
Also, this will not apply to comsumers of hyperlight-guest-bin, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth to recheck and try to remove it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything seems to work without the flag, I remove it for now 🤷‍♂️

@andreiltd andreiltd force-pushed the libc-takeover branch 2 times, most recently from c24ce45 to d9fb9e5 Compare February 25, 2026 11:33
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
@andreiltd andreiltd force-pushed the libc-takeover branch 2 times, most recently from e4caabe to 7b99307 Compare February 25, 2026 17:59
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
@andreiltd andreiltd marked this pull request as ready for review February 26, 2026 20:11

let code = r#"long long x = 0; int main() { return 0; }"#;
let has = if detect_cc_feature(code)? { 1 } else { 0 };
writeln!(file, "#define __IO_LONG_LONG {has}")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this gets writeln!(file, "#undef __IO_LONG_LONG")?; // minimal format alittle while later (178). Do we need this here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we write this file? is there attribution that is needed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is really necessary, I think @jprendes just wrote a Rust implementation for this in hyperlight-js. maybe we could use it?

(void)__tz;

_current_time(current_time);
tv->tv_sec = current_time[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to check for null here?

case CLOCK_REALTIME:
case CLOCK_MONOTONIC:
_current_time(current_time);
tp->tv_sec = current_time[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here?

Comment on lines +52 to +58
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to surface this. We should include it in the PR description I think

Copy link
Contributor

@dblnz dblnz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, Tomasz!! This is one hell of a diff and I like it 😆

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is really necessary, I think @jprendes just wrote a Rust implementation for this in hyperlight-js. maybe we could use it?

pub mod paging;

#[cfg(feature = "libc")]
mod host_bridge;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a place we document or mention the functions needed to be implemented by the host for the guest to have libc work?
I am referring to HostRead and the others from host_bridge.rs

"#
);

let has = if detect_cc_feature(&code)? { 1 } else { 0 };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These here are quite a lot of invocations of clang. Do you think this can be done in a single call? Something like printing an integer for each builtin and then parsing the output to identify which is supported.

.flag("-fno-stack-protector")
.flag("-fstack-clash-protection")
.flag("-mstack-probe-size=4096")
// We don't use a different stack for all interrupts, so there
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you see the process of updating to a new picolibc version?
Checkout the new version, verify copyright headers, check the checked out files against the ones in this file, update this file in case files were removed or added?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's worth mentioning in the readme, to have some instructions


ensure-cargo-hyperlight:
command -v cargo-hyperlight >/dev/null 2>&1 || cargo install --locked cargo-hyperlight
cargo install --locked --force --git https://github.com/hyperlight-dev/cargo-hyperlight --branch picolibc cargo-hyperlight
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes to the "official version of the tool" before merging this PR, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/enhancement For PRs adding features, improving functionality, docs, tests, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants