From 6e140a0d3841786969531f219f3ccce74ae705cf Mon Sep 17 00:00:00 2001 From: "Tate, Hongliang Tian" Date: Fri, 1 May 2020 13:46:14 +0000 Subject: [PATCH] Add errno info for ECalls Before this commit, the three ECalls of the LibOS enclave do not give the exact reason on error. In this commit, we modify the enclave entry code to return the errno and list all possible values of errno in Enclave.edl. --- src/Enclave.edl | 32 ++++++++++++++++++++++++----- src/libos/src/entry.rs | 32 +++++++++++++++-------------- src/libos/src/process/task/exec.rs | 2 +- src/pal/include/errno2str.h | 14 +++++++++++++ src/pal/src/errno2str.c | 21 +++++++++++++++++++ src/pal/src/pal_api.c | 33 ++++++++++++++++++------------ 6 files changed, 100 insertions(+), 34 deletions(-) create mode 100644 src/pal/include/errno2str.h create mode 100644 src/pal/src/errno2str.c diff --git a/src/Enclave.edl b/src/Enclave.edl index ef7617fb..50b9f63b 100644 --- a/src/Enclave.edl +++ b/src/Enclave.edl @@ -13,7 +13,11 @@ enclave { /* * Initialize the LibOS according to the specified attributes. * - * @retval On success, return 0; otherwise, return -1. + * @retval On success, return 0; otherwise, return -errno. + * + * The possible values of errno are + * EEXIST - The LibOS has already been initialized. + * EINVAL - The value of an argument are invalid. */ public int occlum_ecall_init([in, string] const char* log_level, [in, string] const char* instance_dir); @@ -23,7 +27,13 @@ enclave { * * @retval On success, return the thread ID of the * newly-created process (pid == tid for a new process). On error, - * return -1. + * return -errno. + * + * The possible values of errno are + * EAGAIN - The LibOS is not initialized. + * EINVAL - The value of an argument are invalid. + * ENOMEM - Not enough memory to create the new process. + * EACCES - The path of the executable is not accessible. */ public int occlum_ecall_new_process( [in, string] const char* executable_path, @@ -33,10 +43,22 @@ enclave { /* * Execute the LibOS thread specified by the TID. * - * This API is synchronous: it returns until the LibOS thread exits. + * This API is synchronous: it returns until the LibOS thread + * terminates. * - * @retval On success, return the exit status of the thread. On error, - * return -1. + * @retval On success, return the exit status of the thread, whose + * value is always greater than or equal to zero. To unify the two + * cases of terminating normally and being killed by a signal, the + * exit status is encoded in the way described in wait(2) man page. + * This means if the thread exits normally (e.g., returning from the + * main page or call exit(2) explictly), then the exit status is + * can be gained by WEXITSTATUS(status). If the thread is killed by a + * signal, then the signal number is WTERMSIG(status). + * + * The possible values of errno are + * EAGAIN - The LibOS is not initialized. + * EINVAL - The value of an argument are invalid. + * ESRCH - Cannot find the LibOS thread. */ public int occlum_ecall_exec_thread(int libos_tid, int host_tid); }; diff --git a/src/libos/src/entry.rs b/src/libos/src/entry.rs index 7546ab1d..a6a0cdf1 100644 --- a/src/libos/src/entry.rs +++ b/src/libos/src/entry.rs @@ -19,10 +19,17 @@ lazy_static! { static ref HAS_INIT: AtomicBool = AtomicBool::new(false); } +macro_rules! ecall_errno { + ($errno:expr) => {{ + let errno: Errno = $errno; + -(errno as i32) + }}; +} + #[no_mangle] pub extern "C" fn occlum_ecall_init(log_level: *const c_char, instance_dir: *const c_char) -> i32 { if HAS_INIT.load(Ordering::SeqCst) == true { - return EXIT_STATUS_INTERNAL_ERROR; + return ecall_errno!(EEXIST); } assert!(!instance_dir.is_null()); @@ -31,7 +38,7 @@ pub extern "C" fn occlum_ecall_init(log_level: *const c_char, instance_dir: *con let input_log_level = match parse_log_level(log_level) { Err(e) => { eprintln!("invalid log level: {}", e.backtrace()); - return EXIT_STATUS_INTERNAL_ERROR; + return ecall_errno!(EINVAL); } Ok(log_level) => log_level, }; @@ -75,14 +82,14 @@ pub extern "C" fn occlum_ecall_new_process( host_stdio_fds: *const HostStdioFds, ) -> i32 { if HAS_INIT.load(Ordering::SeqCst) == false { - return EXIT_STATUS_INTERNAL_ERROR; + return ecall_errno!(EAGAIN); } let (path, args, host_stdio_fds) = match parse_arguments(path_buf, argv, host_stdio_fds) { Ok(path_and_args_and_host_stdio_fds) => path_and_args_and_host_stdio_fds, Err(e) => { eprintln!("invalid arguments for LibOS: {}", e.backtrace()); - return EXIT_STATUS_INTERNAL_ERROR; + return ecall_errno!(e.errno()); } }; @@ -93,18 +100,18 @@ pub extern "C" fn occlum_ecall_new_process( Ok(pid_t) => pid_t as i32, Err(e) => { eprintln!("failed to boot up LibOS: {}", e.backtrace()); - EXIT_STATUS_INTERNAL_ERROR + ecall_errno!(e.errno()) } } }) }) - .unwrap_or(EXIT_STATUS_INTERNAL_ERROR) + .unwrap_or(ecall_errno!(EFAULT)) } #[no_mangle] pub extern "C" fn occlum_ecall_exec_thread(libos_pid: i32, host_tid: i32) -> i32 { if HAS_INIT.load(Ordering::SeqCst) == false { - return EXIT_STATUS_INTERNAL_ERROR; + return ecall_errno!(EAGAIN); } let _ = unsafe { backtrace::enable_backtrace(&ENCLAVE_PATH, PrintFormat::Short) }; @@ -114,19 +121,14 @@ pub extern "C" fn occlum_ecall_exec_thread(libos_pid: i32, host_tid: i32) -> i32 Ok(exit_status) => exit_status, Err(e) => { eprintln!("failed to execute a process: {}", e.backtrace()); - EXIT_STATUS_INTERNAL_ERROR + ecall_errno!(e.errno()) } } }) }) - .unwrap_or(EXIT_STATUS_INTERNAL_ERROR) + .unwrap_or(ecall_errno!(EFAULT)) } -// Use -128 as a special value to indicate internal error from libos, not from -// user programs. The LibOS ensures that an user program can only return a -// value between 0 and 255 (inclusive). -const EXIT_STATUS_INTERNAL_ERROR: i32 = -128; - fn parse_log_level(level_chars: *const c_char) -> Result { const DEFAULT_LEVEL: LevelFilter = LevelFilter::Off; @@ -249,7 +251,7 @@ fn validate_program_path(target_path: &PathBuf) -> Result<()> { .iter() .any(|valid_path_prefix| target_path.starts_with(valid_path_prefix)); if !is_valid_entry_point { - return_errno!(EINVAL, "program path is NOT a valid entry point"); + return_errno!(EACCES, "program path is NOT a valid entry point"); } Ok(()) } diff --git a/src/libos/src/process/task/exec.rs b/src/libos/src/process/task/exec.rs index ec2bfd89..617dd755 100644 --- a/src/libos/src/process/task/exec.rs +++ b/src/libos/src/process/task/exec.rs @@ -28,7 +28,7 @@ fn dequeue(libos_tid: pid_t) -> Result { .lock() .unwrap() .remove(&libos_tid) - .ok_or_else(|| errno!(EAGAIN, "the given TID does not match any pending thread")) + .ok_or_else(|| errno!(ESRCH, "the given TID does not match any pending thread")) } /// Execute the specified LibOS thread in the current host thread. diff --git a/src/pal/include/errno2str.h b/src/pal/include/errno2str.h new file mode 100644 index 00000000..4861e525 --- /dev/null +++ b/src/pal/include/errno2str.h @@ -0,0 +1,14 @@ +#ifndef __ERRNO2STR_H__ +#define __ERRNO2STR_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +const char* errno2str(int errno_); + +#ifdef __cplusplus +} +#endif + +#endif /* __ERRNO2STR_H__ */ diff --git a/src/pal/src/errno2str.c b/src/pal/src/errno2str.c new file mode 100644 index 00000000..5865e7dd --- /dev/null +++ b/src/pal/src/errno2str.c @@ -0,0 +1,21 @@ +#include +#include "errno2str.h" + +const char* errno2str(int errno_) { + switch(errno_) { + case EPERM: return "EPERM"; + case ENOENT: return "ENOENT"; + case ESRCH: return "ESRCH"; + case ENOEXEC: return "ENOEXEC"; + case EBADF: return "EBADF"; + case ECHILD: return "ECHILD"; + case EAGAIN: return "EAGAIN"; + case ENOMEM: return "ENOMEM"; + case EACCES: return "EACCES"; + case EFAULT: return "EFAULT"; + case EBUSY: return "EBUSY"; + case EINVAL: return "EINVAL"; + case ENOSYS: return "ENOSYS"; + default: return "unknown"; + } +} diff --git a/src/pal/src/pal_api.c b/src/pal/src/pal_api.c index 855b3abd..cb28a189 100644 --- a/src/pal/src/pal_api.c +++ b/src/pal/src/pal_api.c @@ -4,6 +4,7 @@ #include "pal_error.h" #include "pal_log.h" #include "pal_syscall.h" +#include "errno2str.h" int occlum_pal_init(const struct occlum_pal_attr* attr) { errno = 0; @@ -27,21 +28,18 @@ int occlum_pal_init(const struct occlum_pal_attr* attr) { if (pal_init_enclave(attr->instance_dir) < 0) { return -1; } - - // Invoke a do-nothing ECall for two purposes: - // 1) Test the enclave can work; - // 2) Initialize the global data structures inside the enclave (which is - // automatically done by Intel SGX SDK). eid = pal_get_enclave_id(); - int ret; - sgx_status_t ecall_status = occlum_ecall_init(eid, &ret, attr->log_level, attr->instance_dir); + + int ecall_ret = 0; + sgx_status_t ecall_status = occlum_ecall_init(eid, &ecall_ret, attr->log_level, attr->instance_dir); if (ecall_status != SGX_SUCCESS) { const char* sgx_err = pal_get_sgx_error_msg(ecall_status); PAL_ERROR("Failed to do ECall: %s", sgx_err); return -1; } - if (ret < 0) { - errno = EINVAL; + if (ecall_ret < 0) { + errno = -ecall_ret; + PAL_ERROR("occlum_ecall_init returns %s", errno2str(errno)); return -1; } return 0; @@ -65,25 +63,34 @@ int occlum_pal_exec(const char* cmd_path, return -1; } - int libos_tid = -1; - sgx_status_t ecall_status = occlum_ecall_new_process(eid, &libos_tid, cmd_path, cmd_args, io_fds); + int ecall_ret = 0; // libos_tid + sgx_status_t ecall_status = occlum_ecall_new_process(eid, &ecall_ret, cmd_path, cmd_args, io_fds); if (ecall_status != SGX_SUCCESS) { const char* sgx_err = pal_get_sgx_error_msg(ecall_status); PAL_ERROR("Failed to do ECall: %s", sgx_err); return -1; } - if (libos_tid < 0) { + if (ecall_ret < 0) { + errno = -ecall_ret; + PAL_ERROR("occlum_ecall_new_process returns %s", errno2str(errno)); return -1; } + int libos_tid = ecall_ret; int host_tid = gettid(); - ecall_status = occlum_ecall_exec_thread(eid, exit_status, libos_tid, host_tid); + ecall_status = occlum_ecall_exec_thread(eid, &ecall_ret, libos_tid, host_tid); if (ecall_status != SGX_SUCCESS) { const char* sgx_err = pal_get_sgx_error_msg(ecall_status); PAL_ERROR("Failed to do ECall: %s", sgx_err); return -1; } + if (ecall_ret < 0) { + errno = -ecall_ret; + PAL_ERROR("occlum_ecall_exec_thread returns %s", errno2str(errno)); + return -1; + } + *exit_status = ecall_ret; return 0; }