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.
This commit is contained in:
Tate, Hongliang Tian 2020-05-01 13:46:14 +00:00
parent 1c707eda30
commit 6e140a0d38
6 changed files with 100 additions and 34 deletions

@ -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);
};

@ -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<LevelFilter> {
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(())
}

@ -28,7 +28,7 @@ fn dequeue(libos_tid: pid_t) -> Result<ThreadRef> {
.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.

@ -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__ */

21
src/pal/src/errno2str.c Normal file

@ -0,0 +1,21 @@
#include <errno.h>
#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";
}
}

@ -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;
}