Rust Std for UEFI

Implementation of Rust Standard Library for UEFI

5 min, 929 words

Categories: project

This project was created as a part of Google Summer of Code 2022, under the Tianocore organization. It is a working implementation of Rust Standard Library for UEFI targets, with an open upstream PR.

Mentors

This project would not have been possible without the guidance from my excellent mentors:

Special Mentions

Why use Std in UEFI

Since UEFI is a low-level target, some people might be wondering the actual usefulness of this work. While not all UEFI projects would benefit from having std support, here are a few examples:

  1. Writing UEFI shell applications. This includes stuff like benchmarks, self-test utilities, etc.
  2. Finding UEFI target bugs. During this work, I have found 3 numeric tests that cause CPU exceptions for UEFI (they are fixed now. Also, I have found 2 additional bugs (which seem like bugs in llvm soft-float) which went unnoticed because there was no easy way to do any broad testing.
  3. Provide a stable interface for library developers. The current std contains some functions under std::os::uefi::env to provide access to the underlying SystemTable and SystemHandle, which are essential for writing anything for UEFI. This can allow UEFI-related crates to add extra functionality if std is being used.
  4. Now that the entry function has been fixed (see Using Rust main from custom entry point (Part 3)), many drivers can also use Std.

Std Implementation Status

  • alloc
    • GlobalAlloc
      • alloc
      • dealloc
  • args
    • Args
    • args: Implement using EFI_LOADED_IMAGE_PROTOCOL
  • cmath: Provided by compiler_builtins for UEFI.
  • env: Just contains some constants
  • fs
    • File
      • *open: Can only open files in the same volume as executable.
      • file_attr
      • *fsync: Uses EFI_FILE_PROTOCOL.Flush() internally
      • *datasync: Uses fsync internally
      • truncate
      • read
      • *read_vectored: Using rust default implementation.
      • is_read_vectored
      • write
      • *write_vectored: Using rust default implementation.
      • is_write_vectored
      • flush: Don't really maintain any buffer in Rust side.
      • seek
      • duplicate
      • set_permissions
    • FileAttr
      • size
      • perm
      • file_type
      • modified
      • accessed
      • created
    • ReadDir
    • DirEntry
      • path
      • file_name
      • metadata
      • file_type
    • OpenOptions
      • new
      • read
      • write
      • append
      • truncate
      • create
      • create_new
    • FilePermissions
      • readonly
      • set_readonly
    • FileType
      • is_dir
      • is_file
      • *is_symlink: Just returns false since I don't think UEFI supports symlinks.
    • DirBuilder
      • new
      • *mkdir: Can only open files in the same volume as executable.
    • readdir
    • unlink
    • rename
    • set_perm
    • rmdir
    • remove_dir_all
    • try_exists
    • readlink
    • symlink
    • link
    • stat
    • lstat
    • canonicalize
    • copy
  • io
    • IoSlice
      • new
      • advance
      • as_slice
    • IoSliceMut
      • new
      • advance
      • as_slice
      • as_mut_slice
  • *locks: Using the default implementation at unsupported/locks.rs. It should work for any target having just a single-thread.
  • *net: Only implmented TCPv4 right now.
    • TcpStream
      • connect
      • connect_timeout
      • set_read_timeout
      • set_write_timeout
      • read_timeout
      • write_timeout
      • peek
      • read
      • read_vectored
      • is_read_vectored
      • write
      • write_vectored
      • is_write_vectored
      • peer_addr
      • socket_addr
      • *shutdown: Only implemented complete shutdown right now.
      • duplicate
      • linger
      • set_nodelay
      • nodelay
      • set_ttl
      • ttl
      • take_error
      • set_nonblocking
    • TcpListener
      • bind
      • socket_addr
      • accept
      • duplicate
      • set_ttl
      • ttl
      • set_only_v6
      • only_v6
      • take_error
      • set_nonblocking
    • UdpSocket
  • os
    • errno
    • error_string
    • getcwd: Returns the Text representation of EFI_DEVICE_PATH_PROTOCOL. This can be directly converted to EFI_DEVICE_PATH_PROTOCOL using the EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL.
    • chdir
    • SplitPaths
    • split_paths
    • JoinPaths
    • join_paths
    • current_exe: Returns the Text representation of EFI_DEVICE_PATH_PROTOCOL. This can be directly converted to EFI_DEVICE_PATH_PROTOCOL using the EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL.
    • Env
    • env
    • getenv
    • setenv
    • unsetenv
    • temp_dir
    • home_dir
    • exit
    • getpid
  • os_str: Uses Windows implementation
  • path
    • MAIN_SEP_STR
    • MAIN_SEP
    • is_sep_byte
    • is_verbatim_sep
    • parse_prefix: Since std::path::Prefix cannot be extended, this just returns None.
    • absolute
  • pipe
    • *AnonPipe: Implemented using custom protocol
      • read
      • read_vectored: Using default implementation
      • is_read_vectored
      • write
      • write_vectored: Using default implementation
      • is_write_vectored
      • diverge
    • *read2: It is blocking and synchronous right now
  • process
    • Command
      • new
      • arg
      • env_mut
      • cwd
      • stdin
      • stdout
      • stderr
      • get_program
      • get_args
      • get_envs
      • get_current_dir
      • *spawn: Currently calling EFI_BOOT_SERVICES.StartImage() since the Rust Command API seems to assume that Pipes can be operated in parallel.
    • StdioPipes
    • Stdio
    • ExitStatus
      • exit_ok
      • code
    • ExitStatusError
      • code
    • ExitCode
      • as_i32
    • Process
      • id
      • kill
      • wait
      • try_wait
    • CommandArgs
  • stdio
    • STDIN_BUF_SIZE
    • Stdin
      • new
      • *read: Buffered reading not implemented yet.
    • Stdout
      • new
      • write
      • flush
    • Stderr
      • new
      • write
      • flush
  • thread
    • Thread
      • new
      • yield_now
      • set_name
      • sleep
      • join
    • available_parallelism: Just returns 1 since UEFI is single-threaded
  • thread_local_key: Using unsupported/thread_local_key.rs
  • time
    • Instant
      • now
      • checked_sub_instant
      • checked_add_duration
      • checked_sub_duration
    • SystemTime: Implemented using GetTime()
      • now
      • sub_time
      • checked_add_duration
      • checked_sub_duration

Conclusion

I will continue to work on improving UEFI development in Rust in my spare time. Feel free to check out the GitHub PR if you are interested in this project. Also, consider supporting me if you like my work.

Helpful Links