[crates] Implement vdso for time precision

This commit is contained in:
ClawSeven 2024-02-29 15:09:01 +08:00 committed by volcano
parent 9404da7cf8
commit b2f721d1bb
48 changed files with 2108 additions and 111 deletions

@ -6,6 +6,7 @@ enclave {
from "sgx_tprotected_fs.edl" import *;
from "sgx_net.edl" import *;
from "sgx_occlum_utils.edl" import *;
from "sgx_vdso_time_ocalls.edl" import *;
include "sgx_quote.h"
include "occlum_edl_types.h"
@ -148,9 +149,6 @@ enclave {
int occlum_ocall_thread_getcpuclock([out] struct timespec* ts) propagate_errno;
void occlum_ocall_gettimeofday([out] struct timeval* tv);
void occlum_ocall_clock_gettime(clockid_t clockid, [out] struct timespec* ts);
void occlum_ocall_clock_getres(clockid_t clockid, [out] struct timespec* res);
void occlum_ocall_rdtsc([out] uint32_t* low, [out] uint32_t* high);
void occlum_ocall_get_timerslack([out] int *timer_slack);

@ -25,7 +25,8 @@ rcore-fs-devfs = { path = "../../deps/sefs/rcore-fs-devfs" }
resolv-conf = { path = "../../deps/resolv-conf" }
serde = { path = "../../deps/serde-sgx/serde", features = ["derive"] }
serde_json = { path = "../../deps/serde-json-sgx" }
errno = { path = "./crates/errno", features = ["occlum"] }
errno = { path = "crates/errno", features = ["occlum"] }
vdso-time = { path = "crates/vdso-time", default-features = false, features = ["sgx"] }
memoffset = "0.6.1"
scroll = { version = "0.11.0", default-features = false }
itertools = { version = "0.10.0", default-features = false, features = ["use_alloc"] }

@ -162,7 +162,11 @@ $(OBJ_DIR)/libos/$(SRC_OBJ)/Enclave_t.o: $(OBJ_DIR)/libos/$(SRC_OBJ)/Enclave_t.c
@echo "CC <= $@"
$(OBJ_DIR)/libos/$(SRC_OBJ)/Enclave_t.c: $(SGX_EDGER8R) ../Enclave.edl
@cd $(OBJ_DIR)/libos/$(SRC_OBJ) && $(SGX_EDGER8R) $(SGX_EDGER8R_MODE) --trusted $(CUR_DIR)/../Enclave.edl --search-path $(SGX_SDK)/include --search-path $(RUST_SGX_SDK_DIR)/edl
@cd $(OBJ_DIR)/libos/$(SRC_OBJ) && \
$(SGX_EDGER8R) $(SGX_EDGER8R_MODE) --trusted $(CUR_DIR)/../Enclave.edl \
--search-path $(SGX_SDK)/include \
--search-path $(RUST_SGX_SDK_DIR)/edl \
--search-path $(CRATES_DIR)/vdso-time/ocalls
@echo "GEN <= $@"
$(C_OBJS):$(OBJ_DIR)/libos/$(SRC_OBJ)/%.o: src/%.c

10
src/libos/crates/vdso-time/.gitignore vendored Normal file

@ -0,0 +1,10 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

@ -0,0 +1,32 @@
[package]
name = "vdso-time"
version = "0.1.0"
authors = ["Shuocheng Wang <shuocheng.wsc@antgroup.com>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["std"]
std = ["libc"]
sgx = ["sgx_types", "sgx_tstd", "sgx_libc", "sgx_trts"]
[dependencies]
cfg-if = "1.0"
errno = { path = "../errno" }
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
libc = { version = "0.2", optional = true }
log = "0.4"
sgx_types = { path = "../../../../deps/rust-sgx-sdk/sgx_types", optional = true }
sgx_tstd = { path = "../../../../deps/rust-sgx-sdk/sgx_tstd", optional = true, features = ["backtrace"] }
sgx_libc = { path = "../../../../deps/rust-sgx-sdk/sgx_libc", optional = true }
sgx_trts = { path = "../../../../deps/rust-sgx-sdk/sgx_trts", optional = true }
[dev-dependencies]
criterion = "0.3"
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
ctor = "0.1"
[[bench]]
name = "bench"
harness = false

@ -0,0 +1,40 @@
# vdso-time
A rust crate for getting time using vDSO. This crate can support host and SGX (based on Rust-SGX-SDK).
## Getting Started
Add the following dependency to your Cargo manifest:
```
vdso-time = { path = "yourpath/vdso-time" }
```
If you want to use in SGX environment, add the following dependency to your Cargo manifest:
```
vdso-time = { path = "yourpath/vdso-time", default-features = false, features = ["sgx"] }
```
## API examples
```
use vdso_time::ClockId;
let time = vdso_time::clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
println!("vdso_time::clock_gettime: {:?}", time);
let res = vdso_time::clock_getres(ClockId::CLOCK_MONOTONIC).unwrap();
println!("vdso_time::clock_getres: {:?}", res);
```
```
use vdso_time::{Vdso, ClockId};
let vdso = Vdso::new().unwrap();
let time = vdso.clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
println!("vdso.clock_gettime: {:?}", time);
let res = vdso.clock_getres(ClockId::CLOCK_MONOTONIC).unwrap();
println!("vdso.clock_getres: {:?}", res);
}
```

@ -0,0 +1,42 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use vdso_time::ClockId;
fn libc_clock_gettime() -> libc::timespec {
let mut tp = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe {
libc::clock_gettime(ClockId::CLOCK_MONOTONIC as _, &mut tp as *mut _);
}
tp
}
fn libc_clock_getres() -> libc::timespec {
let mut tp = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe {
libc::clock_getres(ClockId::CLOCK_MONOTONIC as _, &mut tp as *mut _);
}
tp
}
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("libc clock_gettime", |b| {
b.iter(|| black_box(libc_clock_gettime()))
});
c.bench_function("vdso clock_gettime", |b| {
b.iter(|| black_box(vdso_time::clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap()))
});
c.bench_function("libc clock_getres", |b| {
b.iter(|| black_box(libc_clock_getres()))
});
c.bench_function("vdso clock_getres", |b| {
b.iter(|| black_box(vdso_time::clock_getres(ClockId::CLOCK_MONOTONIC).unwrap()))
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

@ -0,0 +1,33 @@
include!("common/bench.rs");
fn libc_clock_gettime() -> Duration {
let mut tp = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe {
libc::clock_gettime(ClockId::CLOCK_MONOTONIC as _, &mut tp as *mut _);
}
Duration::new(tp.tv_sec as u64, tp.tv_nsec as u32)
}
fn libc_clock_getres() -> Duration {
let mut tp = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe {
libc::clock_getres(ClockId::CLOCK_MONOTONIC as _, &mut tp as *mut _);
}
Duration::new(tp.tv_sec as u64, tp.tv_nsec as u32)
}
fn libc_benchmarks() {
benchmark("Libc clock_gettime()", libc_clock_gettime);
benchmark("Libc clock_getres()", libc_clock_getres);
}
fn main() {
libc_benchmarks();
vdso_benchmarks();
}

@ -0,0 +1,31 @@
use std::time::Duration;
use vdso_time::{ClockId, clock_getres, clock_gettime};
/// from criterion crate:
/// A function that is opaque to the optimizer, used to prevent the compiler from
/// optimizing away computations in a benchmark.
///
/// This variant is stable-compatible, but it may cause some performance overhead
/// or fail to prevent code from being eliminated.
fn black_box<T>(dummy: T) -> T {
unsafe {
let ret = std::ptr::read_volatile(&dummy);
std::mem::forget(dummy);
ret
}
}
fn benchmark(name: &str, func: impl Fn() -> Duration) {
let start = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
let loops = 1000000;
for _ in 0..loops {
black_box(func());
}
let end = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
println!("[{}] avg_time: {:?} ns", name, (end - start).as_nanos() / loops);
}
fn vdso_benchmarks() {
benchmark("vdso clock_gettime()", || clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap());
benchmark("vdso clock_getres()", || clock_getres(ClockId::CLOCK_MONOTONIC).unwrap());
}

@ -0,0 +1,26 @@
fn first_example() {
use vdso_time::ClockId;
let time = vdso_time::clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
println!("vdso_time::clock_gettime: {:?}", time);
let res = vdso_time::clock_getres(ClockId::CLOCK_MONOTONIC).unwrap();
println!("vdso_time::clock_getres: {:?}", res);
}
fn second_example() {
use vdso_time::{Vdso, ClockId};
let vdso = Vdso::new().unwrap();
let time = vdso.clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
println!("vdso.clock_gettime: {:?}", time);
let res = vdso.clock_getres(ClockId::CLOCK_MONOTONIC).unwrap();
println!("vdso.clock_getres: {:?}", res);
}
fn example() {
first_example();
second_example();
}

@ -0,0 +1,5 @@
include!("common/example.rs");
fn main() {
example();
}

@ -0,0 +1,11 @@
Cargo.lock
Enclave_u.c
Enclave_u.h
Enclave_t.c
Enclave_t.h
app/target
enclave/target
bin/app
*.o
*.a
*.so

@ -0,0 +1,167 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
######## SGX SDK Settings ########
SGX_SDK ?= /opt/sgxsdk
SGX_MODE ?= HW
SGX_ARCH ?= x64
include ../../../common.mk
include $(RUST_SGX_SDK_DIR)/buildenv.mk
OCALLS_DIR := ../../ocalls
ifeq ($(shell getconf LONG_BIT), 32)
SGX_ARCH := x86
else ifeq ($(findstring -m32, $(CXXFLAGS)), -m32)
SGX_ARCH := x86
endif
ifeq ($(SGX_ARCH), x86)
SGX_COMMON_CFLAGS := -m32
SGX_LIBRARY_PATH := $(SGX_SDK)/lib
SGX_ENCLAVE_SIGNER := $(SGX_SDK)/bin/x86/sgx_sign
SGX_EDGER8R := $(SGX_SDK)/bin/x86/sgx_edger8r
else
SGX_COMMON_CFLAGS := -m64
SGX_LIBRARY_PATH := $(SGX_SDK)/lib64
SGX_ENCLAVE_SIGNER := $(SGX_SDK)/bin/x64/sgx_sign
SGX_EDGER8R := $(SGX_SDK)/bin/x64/sgx_edger8r
endif
ifeq ($(SGX_DEBUG), 1)
ifeq ($(SGX_PRERELEASE), 1)
$(error Cannot set SGX_DEBUG and SGX_PRERELEASE at the same time!!)
endif
endif
ifeq ($(SGX_DEBUG), 1)
SGX_COMMON_CFLAGS += -O0 -g
else
SGX_COMMON_CFLAGS += -O2
endif
SGX_COMMON_CFLAGS += -fstack-protector
######## CUSTOM Settings ########
CUSTOM_LIBRARY_PATH := ./lib
CUSTOM_BIN_PATH := ./bin
CUSTOM_EDL_PATH := $(RUST_SGX_SDK_DIR)/edl
CUSTOM_COMMON_PATH := $(RUST_SGX_SDK_DIR)/common
######## EDL Settings ########
Enclave_EDL_Files := enclave/Enclave_t.c enclave/Enclave_t.h app/Enclave_u.c app/Enclave_u.h
######## APP Settings ########
App_Rust_Flags := --release
App_SRC_Files := $(shell find app/ -type f -name '*.rs') $(shell find app/ -type f -name 'Cargo.toml')
App_Include_Paths := -I ./app -I./include -I$(SGX_SDK)/include -I$(CUSTOM_EDL_PATH)
App_C_Flags := $(SGX_COMMON_CFLAGS) -fPIC -Wno-attributes $(App_Include_Paths)
App_Rust_Path := ./app/target/release
App_Enclave_u_Object := lib/libEnclave_u.a
App_Ocall_Object_Name := libvdso_time_ocalls.a
App_Ocall_Object := lib/$(App_Ocall_Object_Name)
App_Name := bin/app
######## Enclave Settings ########
ifneq ($(SGX_MODE), HW)
Trts_Library_Name := sgx_trts_sim
Service_Library_Name := sgx_tservice_sim
else
Trts_Library_Name := sgx_trts
Service_Library_Name := sgx_tservice
endif
Crypto_Library_Name := sgx_tcrypto
KeyExchange_Library_Name := sgx_tkey_exchange
ProtectedFs_Library_Name := sgx_tprotected_fs
RustEnclave_C_Files := $(wildcard ./enclave/*.c)
RustEnclave_C_Objects := $(RustEnclave_C_Files:.c=.o)
RustEnclave_Include_Paths := -I$(CUSTOM_COMMON_PATH)/inc -I$(CUSTOM_EDL_PATH) -I$(SGX_SDK)/include -I$(SGX_SDK)/include/tlibc -I$(SGX_SDK)/include/stlport -I$(SGX_SDK)/include/epid -I ./enclave -I./include
RustEnclave_Link_Libs := -L$(CUSTOM_LIBRARY_PATH) -lenclave
RustEnclave_Compile_Flags := $(SGX_COMMON_CFLAGS) $(ENCLAVE_CFLAGS) $(RustEnclave_Include_Paths)
RustEnclave_Link_Flags := -Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(SGX_LIBRARY_PATH) \
-Wl,--whole-archive -l$(Trts_Library_Name) -Wl,--no-whole-archive \
-Wl,--start-group -lsgx_tstdc -l$(Service_Library_Name) -l$(Crypto_Library_Name) $(RustEnclave_Link_Libs) -Wl,--end-group \
-Wl,--version-script=enclave/Enclave.lds \
$(ENCLAVE_LDFLAGS)
RustEnclave_Name := enclave/enclave.so
Signed_RustEnclave_Name := bin/enclave.signed.so
.PHONY: all
all: $(App_Name) $(Signed_RustEnclave_Name)
######## EDL Objects ########
$(Enclave_EDL_Files): $(SGX_EDGER8R) enclave/Enclave.edl
$(SGX_EDGER8R) --trusted enclave/Enclave.edl --search-path $(OCALLS_DIR) --search-path $(SGX_SDK)/include --search-path $(CUSTOM_EDL_PATH) --trusted-dir enclave
$(SGX_EDGER8R) --untrusted enclave/Enclave.edl --search-path $(OCALLS_DIR) --search-path $(SGX_SDK)/include --search-path $(CUSTOM_EDL_PATH) --untrusted-dir app
@echo "GEN => $(Enclave_EDL_Files)"
######## App Objects ########
app/Enclave_u.o: $(Enclave_EDL_Files)
@$(CC) $(App_C_Flags) -c app/Enclave_u.c -o $@
@echo "CC <= $<"
$(App_Enclave_u_Object): app/Enclave_u.o
$(AR) rcsD $@ $^
$(App_Ocall_Object):
@$(MAKE) -C $(OCALLS_DIR)
@cp $(OCALLS_DIR)/$(App_Ocall_Object_Name) $@
$(App_Name): $(App_Ocall_Object) $(App_Enclave_u_Object) $(App_SRC_Files)
@cd app && SGX_SDK=$(SGX_SDK) cargo build $(App_Rust_Flags)
@echo "Cargo => $@"
mkdir -p bin
cp $(App_Rust_Path)/app ./bin
######## Enclave Objects ########
enclave/Enclave_t.o: $(Enclave_EDL_Files)
@$(CC) $(RustEnclave_Compile_Flags) -c enclave/Enclave_t.c -o $@
@echo "CC <= $<"
$(RustEnclave_Name): enclave enclave/Enclave_t.o
@$(CXX) enclave/Enclave_t.o -o $@ $(RustEnclave_Link_Flags)
@echo "LINK => $@"
$(Signed_RustEnclave_Name): $(RustEnclave_Name)
mkdir -p bin
@$(SGX_ENCLAVE_SIGNER) sign -key enclave/Enclave_private.pem -enclave $(RustEnclave_Name) -out $@ -config enclave/Enclave.config.xml
@echo "SIGN => $@"
.PHONY: enclave
enclave:
$(MAKE) -C ./enclave/
.PHONY: clean
clean:
@rm -f $(App_Name) $(RustEnclave_Name) $(Signed_RustEnclave_Name) enclave/*_t.* app/*_u.* lib/*.a
@cd enclave && cargo clean && rm -f Cargo.lock
@cd app && cargo clean && rm -f Cargo.lock

@ -0,0 +1,11 @@
## example for SGX
This is an example of using vdso-time in SGX.
This example combines vdso-time example of io_uring and hello-rust example of incubator-teaclave-sgx-sdk.
- ./app : untrusted code
- ./bin : executable program
- ./enclave : trusted code
- ./lib : library
### run example in SGX
1. ```make```
2. ```cd bin && ./app```

@ -0,0 +1,10 @@
[package]
name = "app"
version = "1.0.0"
build = "build.rs"
[dependencies]
sgx_types = { path = "../../../../../../../deps/rust-sgx-sdk/sgx_types" }
sgx_urts = { path = "../../../../../../../deps/rust-sgx-sdk/sgx_urts" }
[workspace]

@ -0,0 +1,34 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License..
use std::env;
fn main() {
let sdk_dir = env::var("SGX_SDK").unwrap_or_else(|_| "/opt/sgxsdk".to_string());
let is_sim = env::var("SGX_MODE").unwrap_or_else(|_| "HW".to_string());
println!("cargo:rustc-link-search=native=../lib");
println!("cargo:rustc-link-lib=static=Enclave_u");
println!("cargo:rustc-link-lib=static=vdso_time_ocalls");
println!("cargo:rustc-link-search=native={}/lib64", sdk_dir);
match is_sim.as_ref() {
"SW" => println!("cargo:rustc-link-lib=dylib=sgx_urts_sim"),
"HW" => println!("cargo:rustc-link-lib=dylib=sgx_urts"),
_ => println!("cargo:rustc-link-lib=dylib=sgx_urts"), // Treat undefined as HW
}
}

@ -0,0 +1 @@
nightly-2020-10-25

@ -0,0 +1,78 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License..
extern crate sgx_types;
extern crate sgx_urts;
use sgx_types::*;
use sgx_urts::SgxEnclave;
static ENCLAVE_FILE: &'static str = "enclave.signed.so";
extern "C" {
fn run_sgx_example(eid: sgx_enclave_id_t, retval: *mut sgx_status_t) -> sgx_status_t;
}
fn init_enclave() -> SgxResult<SgxEnclave> {
let mut launch_token: sgx_launch_token_t = [0; 1024];
let mut launch_token_updated: i32 = 0;
// call sgx_create_enclave to initialize an enclave instance
// Debug Support: set 2nd parameter to 1
let debug = 1;
let mut misc_attr = sgx_misc_attribute_t {
secs_attr: sgx_attributes_t { flags: 0, xfrm: 0 },
misc_select: 0,
};
SgxEnclave::create(
ENCLAVE_FILE,
debug,
&mut launch_token,
&mut launch_token_updated,
&mut misc_attr,
)
}
fn main() {
let enclave = match init_enclave() {
Ok(r) => {
println!("[+] Init Enclave Successful {}!", r.geteid());
r
}
Err(x) => {
println!("[-] Init Enclave Failed {}!", x.as_str());
return;
}
};
let mut retval = sgx_status_t::SGX_SUCCESS;
let result = unsafe { run_sgx_example(enclave.geteid(), &mut retval) };
match result {
sgx_status_t::SGX_SUCCESS => {}
_ => {
println!("[-] ECALL Enclave Failed {}!", result.as_str());
return;
}
}
match retval {
sgx_status_t::SGX_SUCCESS => {}
_ => {
println!("[-] ECALL Returned Error {}!", retval.as_str());
return;
}
}
println!("[+] run_sgx_example success...");
enclave.destroy();
}

@ -0,0 +1 @@
bin

@ -0,0 +1,22 @@
[package]
name = "Helloworldsampleenclave"
version = "1.0.0"
[lib]
name = "helloworldsampleenclave"
crate-type = ["staticlib"]
[features]
default = []
[dependencies]
vdso-time = { path = "../../../../vdso-time", default-features = false, features = ["sgx"] }
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
[target.'cfg(not(target_env = "sgx"))'.dependencies]
sgx_types = { path = "../../../../../../../deps/rust-sgx-sdk/sgx_types" }
sgx_tstd = { path = "../../../../../../../deps/rust-sgx-sdk/sgx_tstd", features = ["backtrace", "thread"] }
sgx_trts = { path = "../../../../../../../deps/rust-sgx-sdk/sgx_trts" }
sgx_libc = { path = "../../../../../../../deps/rust-sgx-sdk/sgx_libc" }
[workspace]

@ -0,0 +1,13 @@
<!-- Please refer to User's Guide for the explanation of each field -->
<EnclaveConfiguration>
<ProdID>0</ProdID>
<ISVSVN>0</ISVSVN>
<StackMaxSize>0x40000</StackMaxSize>
<HeapMaxSize>0x400000</HeapMaxSize>
<TCSNum>1</TCSNum>
<TCSMaxNum>1</TCSMaxNum>
<TCSPolicy>0</TCSPolicy>
<DisableDebug>0</DisableDebug>
<MiscSelect>0</MiscSelect>
<MiscMask>0xFFFFFFFF</MiscMask>
</EnclaveConfiguration>

@ -0,0 +1,36 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
enclave {
from "sgx_tstd.edl" import *;
from "sgx_stdio.edl" import *;
from "sgx_backtrace.edl" import *;
from "sgx_tstdc.edl" import *;
from "sgx_net.edl" import *;
from "sgx_thread.edl" import *;
from "sgx_vdso_time_ocalls.edl" import *;
trusted {
/* define ECALLs here. */
public sgx_status_t run_sgx_example();
};
untrusted {
/* define OCALLs here. */
};
};

@ -0,0 +1,9 @@
enclave.so
{
global:
g_global_data_sim;
g_global_data;
enclave_entry;
local:
*;
};

@ -0,0 +1,39 @@
-----BEGIN RSA PRIVATE KEY-----
MIIG4gIBAAKCAYEAroOogvsj/fZDZY8XFdkl6dJmky0lRvnWMmpeH41Bla6U1qLZ
AmZuyIF+mQC/cgojIsrBMzBxb1kKqzATF4+XwPwgKz7fmiddmHyYz2WDJfAjIveJ
ZjdMjM4+EytGlkkJ52T8V8ds0/L2qKexJ+NBLxkeQLfV8n1mIk7zX7jguwbCG1Pr
nEMdJ3Sew20vnje+RsngAzdPChoJpVsWi/K7cettX/tbnre1DL02GXc5qJoQYk7b
3zkmhz31TgFrd9VVtmUGyFXAysuSAb3EN+5VnHGr0xKkeg8utErea2FNtNIgua8H
ONfm9Eiyaav1SVKzPHlyqLtcdxH3I8Wg7yqMsaprZ1n5A1v/levxnL8+It02KseD
5HqV4rf/cImSlCt3lpRg8U5E1pyFQ2IVEC/XTDMiI3c+AR+w2jSRB3Bwn9zJtFlW
KHG3m1xGI4ck+Lci1JvWWLXQagQSPtZTsubxTQNx1gsgZhgv1JHVZMdbVlAbbRMC
1nSuJNl7KPAS/VfzAgEDAoIBgHRXxaynbVP5gkO0ug6Qw/E27wzIw4SmjsxG6Wpe
K7kfDeRskKxESdsA/xCrKkwGwhcx1iIgS5+Qscd1Yg+1D9X9asd/P7waPmWoZd+Z
AhlKwhdPsO7PiF3e1AzHhGQwsUTt/Y/aSI1MpHBvy2/s1h9mFCslOUxTmWw0oj/Q
ldIEgWeNR72CE2+jFIJIyml6ftnb6qzPiga8Bm48ubKh0kvySOqnkmnPzgh+JBD6
JnBmtZbfPT97bwTT+N6rnPqOOApvfHPf15kWI8yDbprG1l4OCUaIUH1AszxLd826
5IPM+8gINLRDP1MA6azECPjTyHXhtnSIBZCyWSVkc05vYmNXYUNiXWMajcxW9M02
wKzFELO8NCEAkaTPxwo4SCyIjUxiK1LbQ9h8PSy4c1+gGP4LAMR8xqP4QKg6zdu9
osUGG/xRe/uufgTBFkcjqBHtK5L5VI0jeNIUAgW/6iNbYXjBMJ0GfauLs+g1VsOm
WfdgXzsb9DYdMa0OXXHypmV4GwKBwQDUwQj8RKJ6c8cT4vcWCoJvJF00+RFL+P3i
Gx2DLERxRrDa8AVGfqaCjsR+3vLgG8V/py+z+dxZYSqeB80Qeo6PDITcRKoeAYh9
xlT3LJOS+k1cJcEmlbbO2IjLkTmzSwa80fWexKu8/Xv6vv15gpqYl1ngYoqJM3pd
vzmTIOi7MKSZ0WmEQavrZj8zK4endE3v0eAEeQ55j1GImbypSf7Idh7wOXtjZ7WD
Dg6yWDrri+AP/L3gClMj8wsAxMV4ZR8CgcEA0fzDHkFa6raVOxWnObmRoDhAtE0a
cjUj976NM5yyfdf2MrKy4/RhdTiPZ6b08/lBC/+xRfV3xKVGzacm6QjqjZrUpgHC
0LKiZaMtccCJjLtPwQd0jGQEnKfMFaPsnhOc5y8qVkCzVOSthY5qhz0XNotHHFmJ
gffVgB0iqrMTvSL7IA2yqqpOqNRlhaYhNl8TiFP3gIeMtVa9rZy31JPgT2uJ+kfo
gV7sdTPEjPWZd7OshGxWpT6QfVDj/T9T7L6tAoHBAI3WBf2DFvxNL2KXT2QHAZ9t
k3imC4f7U+wSE6zILaDZyzygA4RUbwG0gv8/TJVn2P/Eynf76DuWHGlaiLWnCbSz
Az2DHBQBBaku409zDQym3j1ugMRjzzSQWzJg0SIyBH3hTmnYcn3+Uqcp/lEBvGW6
O+rsXFt3pukqJmIV8HzLGGaLm62BHUeZf3dyWm+i3p/hQAL7Xvu04QW70xuGqdr5
afV7p5eaeQIJXyGQJ0eylV/90+qxjMKiB1XYg6WYvwKBwQCL/ddpgOdHJGN8uRom
e7Zq0Csi3hGheMKlKbN3vcxT5U7MdyHtTZZOJbTvxKNNUNYH/8uD+PqDGNneb29G
BfGzvI3EASyLIcGZF3OhKwZd0jUrWk2y7Vhob91jwp2+t73vdMbkKyI4mHOuXvGv
fg95si9oO7EBT+Oqvhccd2J+F1IVXncccYnF4u5ZGWt5lLewN/pVr7MjjykeaHqN
t+rfnQam2psA6fL4zS2zTmZPzR2tnY8Y1GBTi0Ko1OKd1HMCgcAb5cB/7/AQlhP9
yQa04PLH9ygQkKKptZp7dy5WcWRx0K/hAHRoi2aw1wZqfm7VBNu2SLcs90kCCCxp
6C5sfJi6b8NpNbIPC+sc9wsFr7pGo9SFzQ78UlcWYK2Gu2FxlMjonhka5hvo4zvg
WxlpXKEkaFt3gLd92m/dMqBrHfafH7VwOJY2zT3WIpjwuk0ZzmRg5p0pG/svVQEH
NZmwRwlopysbR69B/n1nefJ84UO50fLh5s5Zr3gBRwbWNZyzhXk=
-----END RSA PRIVATE KEY-----

@ -0,0 +1,38 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
Rust_Enclave_Name := libenclave.a
Rust_Enclave_Files := $(wildcard src/*.rs)
Rust_Target_Path := $(CURDIR)/../../../../../../deps/rust-sgx-sdk/xargo
ifeq ($(MITIGATION-CVE-2020-0551), LOAD)
export MITIGATION_CVE_2020_0551=LOAD
else ifeq ($(MITIGATION-CVE-2020-0551), CF)
export MITIGATION_CVE_2020_0551=CF
endif
.PHONY: all
all: $(Rust_Enclave_Name)
$(Rust_Enclave_Name): $(Rust_Enclave_Files)
ifeq ($(XARGO_SGX), 1)
RUST_TARGET_PATH=$(Rust_Target_Path) xargo build --target x86_64-unknown-linux-sgx --release
cp ./target/x86_64-unknown-linux-sgx/release/libhelloworldsampleenclave.a ../lib/libenclave.a
else
cargo build --release
cp ./target/release/libhelloworldsampleenclave.a ../lib/libenclave.a
endif

@ -0,0 +1 @@
nightly-2020-10-25

@ -0,0 +1,49 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License..
#![crate_name = "helloworldsampleenclave"]
#![crate_type = "staticlib"]
#![cfg_attr(not(target_env = "sgx"), no_std)]
#![cfg_attr(target_env = "sgx", feature(rustc_private))]
extern crate sgx_trts;
extern crate sgx_types;
#[cfg(not(target_env = "sgx"))]
#[macro_use]
extern crate sgx_tstd as std;
extern crate sgx_libc as libc;
extern crate vdso_time;
extern crate lazy_static;
use sgx_types::*;
use std::prelude::v1::*;
include!("../../../common/example.rs");
include!("../../../common/bench.rs");
#[no_mangle]
pub extern "C" fn run_sgx_example() -> sgx_status_t {
// std::backtrace::enable_backtrace("enclave.signed.so", std::backtrace::PrintFormat::Full);
println!("[ECALL] run_sgx_example");
example();
vdso_benchmarks();
sgx_status_t::SGX_SUCCESS
}

@ -0,0 +1,31 @@
{
"arch": "x86_64",
"cpu": "x86-64",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"dynamic-linking": true,
"env": "sgx",
"exe-allocation-crate": "alloc_system",
"executables": true,
"has-elf-tls": true,
"has-rpath": true,
"linker-flavor": "gcc",
"linker-is-gnu": true,
"llvm-target": "x86_64-unknown-linux-gnu",
"max-atomic-width": 64,
"os": "linux",
"position-independent-executables": true,
"pre-link-args": {
"gcc": [
"-Wl,--as-needed",
"-Wl,-z,noexecstack",
"-m64"
]
},
"relro-level": "full",
"stack-probes": true,
"target-c-int-width": "32",
"target-endian": "little",
"target-family": "unix",
"target-pointer-width": "64",
"vendor": "mesalock"
}

@ -0,0 +1 @@
lib

@ -0,0 +1,3 @@
*.o
*.a
*.so

@ -0,0 +1,12 @@
all: libs
libs: libvdso_time_ocalls.a
libvdso_time_ocalls.a: vdso_time_ocalls.o
ar rcs $@ $^
vdso_time_ocalls.o: vdso-time-ocalls.c
gcc -O3 -c -o $@ $<
clean:
rm -f *.o *.a

@ -0,0 +1,14 @@
enclave {
include "time.h"
untrusted {
int vdso_ocall_get_vdso_info(
[out] unsigned long* vdso_addr,
[out, size = release_len] char* release,
int release_len
);
int vdso_ocall_clock_gettime(int clockid, [out] struct timespec* ts);
int vdso_ocall_clock_getres(int clockid, [out] struct timespec* res);
};
};

@ -0,0 +1,30 @@
#include <sys/auxv.h>
#include <sys/utsname.h>
#include <time.h>
#include <string.h>
int vdso_ocall_get_vdso_info(
unsigned long *vdso_addr,
char *release,
int release_len) {
// If AT_SYSINFO_EHDR isn't found, getauxval will return 0.
*vdso_addr = getauxval(AT_SYSINFO_EHDR);
struct utsname buf;
int ret = uname(&buf);
// uname should always succeed here, since uname only fails when buf is not invalid.
if (ret != 0) { return -1; }
strncpy(release, buf.release, release_len);
release[release_len - 1] = '\0';
return 0;
}
int vdso_ocall_clock_gettime(int clockid, struct timespec *tp) {
return clock_gettime(clockid, tp);
}
int vdso_ocall_clock_getres(int clockid, struct timespec *res) {
return clock_getres(clockid, res);
}

@ -0,0 +1,622 @@
#![cfg_attr(feature = "sgx", no_std)]
#[cfg(feature = "sgx")]
extern crate sgx_types;
#[cfg(feature = "sgx")]
#[macro_use]
extern crate sgx_tstd as std;
#[cfg(feature = "sgx")]
extern crate sgx_libc as libc;
#[cfg(feature = "sgx")]
extern crate sgx_trts;
mod sys;
use errno::prelude::*;
use lazy_static::lazy_static;
use log::trace;
use std::convert::TryFrom;
use std::time::Duration;
use std::{hint, str};
use sys::*;
pub const NANOS_PER_SEC: u32 = 1_000_000_000;
pub const NANOS_PER_MILLI: u32 = 1_000_000;
pub const NANOS_PER_MICRO: u32 = 1_000;
pub const MILLIS_PER_SEC: u64 = 1_000;
pub const MICROS_PER_SEC: u64 = 1_000_000;
/// Clocks supported by the linux kernel, corresponding to clockid_t in Linux.
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
#[allow(non_camel_case_types)]
pub enum ClockId {
CLOCK_REALTIME = 0,
CLOCK_MONOTONIC = 1,
// vDSO doesn't support CLOCK_PROCESS_CPUTIME_ID.
CLOCK_PROCESS_CPUTIME_ID = 2,
// vDSO doesn't support CLOCK_THREAD_CPUTIME_ID.
CLOCK_THREAD_CPUTIME_ID = 3,
CLOCK_MONOTONIC_RAW = 4,
CLOCK_REALTIME_COARSE = 5,
CLOCK_MONOTONIC_COARSE = 6,
CLOCK_BOOTTIME = 7,
}
impl TryFrom<i32> for ClockId {
type Error = Error;
fn try_from(clockid: i32) -> Result<Self> {
Ok(match clockid {
0 => ClockId::CLOCK_REALTIME,
1 => ClockId::CLOCK_MONOTONIC,
2 => ClockId::CLOCK_PROCESS_CPUTIME_ID,
3 => ClockId::CLOCK_THREAD_CPUTIME_ID,
4 => ClockId::CLOCK_MONOTONIC_RAW,
5 => ClockId::CLOCK_REALTIME_COARSE,
6 => ClockId::CLOCK_MONOTONIC_COARSE,
7 => ClockId::CLOCK_BOOTTIME,
_ => return_errno!(EINVAL, "Unsupported clockid"),
})
}
}
/// An abstraction of Linux vDSO provides the clock and time interface through Linux vDSO.
pub struct Vdso {
vdso_data_ptr: VdsoDataPtr,
// hres resolution for clock_getres
hres_resolution: Option<Duration>,
// coarse resolution for clock_getres
coarse_resolution: Option<Duration>,
}
impl Vdso {
/// Try to create a new Vdso by libc or SGX OCALL.
///
/// # Examples
///
/// ```
/// use vdso_time::Vdso;
/// let vdso = Vdso::new().unwrap();
/// ```
pub fn new() -> Result<Self> {
let vdso_data_ptr = Self::get_vdso_data_ptr_from_host()?;
let hres_resolution = clock_getres_slow(ClockId::CLOCK_MONOTONIC).ok();
let coarse_resolution = clock_getres_slow(ClockId::CLOCK_MONOTONIC_COARSE).ok();
let vdso = Self {
vdso_data_ptr,
hres_resolution,
coarse_resolution,
};
vdso.check_accuracy()?;
Ok(vdso)
}
#[cfg(feature = "sgx")]
fn get_vdso_data_ptr_from_host() -> Result<VdsoDataPtr> {
extern "C" {
fn vdso_ocall_get_vdso_info(
ret: *mut libc::c_int,
vdso_addr: *mut libc::c_ulong,
release: *mut libc::c_char,
release_len: libc::c_int,
) -> sgx_types::sgx_status_t;
}
let mut vdso_addr: libc::c_ulong = 0;
let mut release = [0 as libc::c_char; 65];
let mut ret: libc::c_int = 0;
unsafe {
vdso_ocall_get_vdso_info(
&mut ret as *mut _,
&mut vdso_addr as *mut _,
release.as_mut_ptr(),
release.len() as _,
);
}
if ret != 0 {
return_errno!(EINVAL, "Vdso vdso_ocall_get_vdso_info() failed")
}
Self::match_kernel_version(vdso_addr, &release)
}
#[cfg(not(feature = "sgx"))]
fn get_vdso_data_ptr_from_host() -> Result<VdsoDataPtr> {
const AT_SYSINFO_EHDR: u64 = 33;
let vdso_addr = unsafe { libc::getauxval(AT_SYSINFO_EHDR) };
let mut utsname: libc::utsname = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::uname(&mut utsname as *mut _) };
if ret != 0 {
return_errno!(EINVAL, "Vdso get utsname failed");
}
let release = utsname.release;
Self::match_kernel_version(vdso_addr, &release)
}
fn check_vdso_addr(vdso_addr: &u64) -> Result<()> {
let vdso_addr = *vdso_addr;
if vdso_addr == 0 {
return_errno!(EFAULT, "Vdso vdso_addr is 0")
}
const VDSO_DATA_MAX_SIZE: u64 = 4 * PAGE_SIZE;
if vdso_addr < VDSO_DATA_MAX_SIZE {
return_errno!(EFAULT, "Vdso vdso_addr is less than vdso data size");
}
#[cfg(feature = "sgx")]
if !sgx_trts::trts::rsgx_raw_is_outside_enclave(
(vdso_addr - VDSO_DATA_MAX_SIZE) as *const u8,
VDSO_DATA_MAX_SIZE as _,
) {
return_errno!(EFAULT, "Vdso vdso_addr we got is not outside enclave")
}
Ok(())
}
fn match_kernel_version(vdso_addr: u64, release: &[libc::c_char]) -> Result<VdsoDataPtr> {
Self::check_vdso_addr(&vdso_addr)?;
// release, e.g., "5.9.6-050906-generic"
let release = unsafe { &*(release as *const [i8] as *const [u8]) };
let release = str::from_utf8(release);
if release.is_err() {
return_errno!(EINVAL, "Vdso get kernel release failed")
}
let mut release = release.unwrap().split(&['-', '.', ' '][..]);
let version_big: u8 = release
.next()
.ok_or(errno!(EINVAL, "Vdso get kernel big version failed"))?
.parse()?;
let version_little: u8 = release
.next()
.ok_or(errno!(EINVAL, "Vdso get kernel little version failed"))?
.parse()?;
Ok(match (version_big, version_little) {
(4, 0..=4) | (4, 7..=11) => VdsoDataPtr::V4_0(vdso_data_v4_0::vdsodata_ptr(vdso_addr)),
(4, 5..=6) | (4, 12..=19) => VdsoDataPtr::V4_5(vdso_data_v4_5::vdsodata_ptr(vdso_addr)),
(5, 0..=2) => VdsoDataPtr::V5_0(vdso_data_v5_0::vdsodata_ptr(vdso_addr)),
(5, 3..=5) => VdsoDataPtr::V5_3(vdso_data_v5_3::vdsodata_ptr(vdso_addr)),
(5, 6..=8) => VdsoDataPtr::V5_6(vdso_data_v5_6::vdsodata_ptr(vdso_addr)),
(5, 9..=19) | (6, 0..=2) => VdsoDataPtr::V5_9(vdso_data_v5_9::vdsodata_ptr(vdso_addr)),
(_, _) => return_errno!(EINVAL, "Vdso match kernel release failed"),
})
}
/// Compare the results of Linux syscall and vdso to check whether vdso can support the clockid correctly.
fn check_accuracy(&self) -> Result<()> {
let vdso_supported_clockids = [
ClockId::CLOCK_REALTIME,
ClockId::CLOCK_MONOTONIC,
ClockId::CLOCK_MONOTONIC_RAW,
ClockId::CLOCK_REALTIME_COARSE,
ClockId::CLOCK_MONOTONIC_COARSE,
ClockId::CLOCK_BOOTTIME,
];
const MAX_INACCURACY: Duration = Duration::from_millis(1);
const MAX_RETRY_NUM: u32 = 3;
for &clockid in vdso_supported_clockids.iter() {
for retry_num in 0..MAX_RETRY_NUM {
let time = match self.do_clock_gettime(clockid) {
Ok(time) => time,
Err(_) => break,
};
let host_time = match clock_gettime_slow(clockid) {
Ok(host_time) => host_time,
Err(_) => break,
};
let estimated_inaccuracy = match host_time.checked_sub(time) {
Some(diff) => diff,
None => return_errno!(EOPNOTSUPP, "Vdso can not provide valid time"),
};
if estimated_inaccuracy > MAX_INACCURACY {
if retry_num == MAX_RETRY_NUM - 1 {
return_errno!(EOPNOTSUPP, "Vdso reached max retry number");
}
continue;
}
break;
}
}
Ok(())
}
/// Try to get time according to ClockId.
/// Firstly try to get time through vDSO, if failed, then try fallback.
///
/// # Examples
///
/// ```
/// use vdso_time::{Vdso, ClockId};
/// let vdso = Vdso::new().unwrap();
/// let time = vdso.clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
/// println!("{:?}", time);
/// ```
pub fn clock_gettime(&self, clockid: ClockId) -> Result<Duration> {
self.do_clock_gettime(clockid)
.or_else(|_| clock_gettime_slow(clockid))
}
/// Try to get time resolution according to ClockId.
/// Firstly try to return resolution inside self, if failed, then try fallback.
///
/// # Examples
///
/// ```
/// use vdso_time::{Vdso, ClockId};
/// let vdso = Vdso::new().unwrap();
/// let res = vdso.clock_getres(ClockId::CLOCK_MONOTONIC).unwrap();
/// println!("{:?}", res);
/// ```
pub fn clock_getres(&self, clockid: ClockId) -> Result<Duration> {
self.do_clock_getres(clockid)
.or_else(|_| clock_getres_slow(clockid))
}
fn do_clock_gettime(&self, clockid: ClockId) -> Result<Duration> {
match clockid {
ClockId::CLOCK_REALTIME | ClockId::CLOCK_MONOTONIC | ClockId::CLOCK_BOOTTIME => {
self.do_hres(ClockSource::CS_HRES_COARSE, clockid)
}
ClockId::CLOCK_MONOTONIC_RAW => self.do_hres(ClockSource::CS_RAW, clockid),
ClockId::CLOCK_REALTIME_COARSE | ClockId::CLOCK_MONOTONIC_COARSE => {
self.do_coarse(ClockSource::CS_HRES_COARSE, clockid)
}
// TODO: support CLOCK_PROCESS_CPUTIME_ID and CLOCK_THREAD_CPUTIME_ID.
_ => return_errno!(EINVAL, "Unsupported clockid in do_clock_gettime()"),
}
}
fn do_clock_getres(&self, clockid: ClockId) -> Result<Duration> {
match clockid {
ClockId::CLOCK_REALTIME
| ClockId::CLOCK_MONOTONIC
| ClockId::CLOCK_BOOTTIME
| ClockId::CLOCK_MONOTONIC_RAW => self
.hres_resolution
.ok_or(errno!(EOPNOTSUPP, "hres_resolution is none")),
ClockId::CLOCK_REALTIME_COARSE | ClockId::CLOCK_MONOTONIC_COARSE => self
.coarse_resolution
.ok_or(errno!(EOPNOTSUPP, "coarse_resolution is none")),
// TODO: support CLOCK_PROCESS_CPUTIME_ID and CLOCK_THREAD_CPUTIME_ID.
_ => return_errno!(EINVAL, "Unsupported clockid in do_clock_getres()"),
}
}
fn vdso_data(&self, cs: ClockSource) -> &'static dyn VdsoData {
match self.vdso_data_ptr {
VdsoDataPtr::V4_0(ptr) => unsafe { &*(ptr) },
VdsoDataPtr::V4_5(ptr) => unsafe { &*(ptr) },
VdsoDataPtr::V5_0(ptr) => unsafe { &*(ptr) },
VdsoDataPtr::V5_3(ptr) => unsafe { &*(ptr.add(cs as _)) },
VdsoDataPtr::V5_6(ptr) => unsafe { &*(ptr.add(cs as _)) },
VdsoDataPtr::V5_9(ptr) => unsafe { &*(ptr.add(cs as _)) },
}
}
fn do_hres(&self, cs: ClockSource, clockid: ClockId) -> Result<Duration> {
let vdso_data = self.vdso_data(cs);
loop {
let seq = vdso_data.seq();
// if seq is odd, it might means that a concurrent update is in progress.
// Hence, we do some instructions to spin waiting for seq to become even again.
if seq & 1 != 0 {
hint::spin_loop();
continue;
}
// Make sure that all prior load-from-memory instructions have completed locally,
// and no later instruction begins execution until LFENCE completes.
// We want to make sure the execution order as followning:
// seq -> [cycles, cycle_last, mult, shift, sec, secs] -> seq
// This LFENCE can ensure that the first seq is before [cycles, cycle_last, mult, shift, sec, secs]
lfence();
// Get hardware counter according to vdso_data's clock_mode.
let cycles = Self::get_hw_counter(vdso_data)?;
let cycle_last = vdso_data.cycle_last();
let mult = vdso_data.mult();
let shift = vdso_data.shift();
let secs = vdso_data.sec(clockid as _)?;
let mut nanos = vdso_data.nsec(clockid as _)?;
if !Self::vdso_read_retry(vdso_data, seq) {
// On x86 arch, the TSC can be slightly off across sockets,
// which might cause cycles < cycle_last. Since they are u64 type,
// cycles - cycle_last will panic in this case.
// Hence we need to verify that cycles is greater than cycle_last.
// If not then just use cycle_last, which is the base time of the
// current conversion period.
// And the vdso mask is always u64_MAX on x86, we don't need use mask.
if cycles > cycle_last {
nanos += (cycles - cycle_last) * mult as u64
}
nanos = nanos >> shift;
return Ok(Duration::new(secs, nanos as u32));
}
}
}
fn do_coarse(&self, cs: ClockSource, clockid: ClockId) -> Result<Duration> {
let vdso_data = self.vdso_data(cs);
loop {
let seq = vdso_data.seq();
// see comments in do_hres
if seq & 1 != 0 {
hint::spin_loop();
continue;
}
// see comments in do_hres
lfence();
let secs = vdso_data.sec(clockid as _)?;
let nanos = vdso_data.nsec(clockid as _)?;
if !Self::vdso_read_retry(vdso_data, seq) {
return Ok(Duration::new(secs, nanos as u32));
}
}
}
fn vdso_read_retry(vdso_data: &dyn VdsoData, old_seq: u32) -> bool {
// Make sure that all prior load-from-memory instructions have completed locally,
// and no later instruction begins execution until LFENCE completes
lfence();
old_seq != vdso_data.seq()
}
fn get_hw_counter(vdso_data: &dyn VdsoData) -> Result<u64> {
let clock_mode = vdso_data.clock_mode();
if clock_mode == VdsoClockMode::VDSO_CLOCKMODE_TSC as i32 {
return Ok(rdtsc_ordered());
} else if clock_mode == VdsoClockMode::VDSO_CLOCKMODE_PVCLOCK as i32 {
// TODO: support pvclock
return_errno!(
EOPNOTSUPP,
"VDSO_CLOCKMODE_PVCLOCK support is not implemented"
);
} else if clock_mode == VdsoClockMode::VDSO_CLOCKMODE_HVCLOCK as i32 {
// TODO: support hvclock
return_errno!(
EOPNOTSUPP,
"VDSO_CLOCKMODE_HVCLOCK support is not implemented"
);
} else if clock_mode == VdsoClockMode::VDSO_CLOCKMODE_TIMENS as i32 {
// TODO: support timens
return_errno!(
EOPNOTSUPP,
"VDSO_CLOCKMODE_TIMENS support is not implemented"
);
} else if clock_mode == VdsoClockMode::VDSO_CLOCKMODE_NONE as i32 {
// In x86 Linux, the clock_mode will never be VDSO_CLOCKMODE_NONE.
return_errno!(EINVAL, "The clock_mode must not be VDSO_CLOCKMODE_NONE");
}
return_errno!(EINVAL, "Unsupported clock_mode");
}
}
unsafe impl Sync for Vdso {}
unsafe impl Send for Vdso {}
lazy_static! {
static ref VDSO: Option<Vdso> = Vdso::new().map_or_else(
|e| {
trace!("{}", e);
None
},
|v| Some(v)
);
}
/// Try to get time according to ClockId.
/// Firstly try to get time through vDSO, if failed, then try fallback.
///
/// # Examples
///
/// ```
/// use vdso_time::ClockId;
///
/// let time = vdso_time::clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
/// println!("{:?}", time);
/// ```
pub fn clock_gettime(clockid: ClockId) -> Result<Duration> {
if VDSO.is_none() {
clock_gettime_slow(clockid)
} else {
VDSO.as_ref().unwrap().clock_gettime(clockid)
}
}
/// Try to get time resolution according to ClockId.
/// Firstly try to get time through vDSO, if failed, then try fallback.
///
/// # Examples
///
/// ```
/// use vdso_time::ClockId;
///
/// let time = vdso_time::clock_getres(ClockId::CLOCK_MONOTONIC).unwrap();
/// println!("{:?}", time);
/// ```
pub fn clock_getres(clockid: ClockId) -> Result<Duration> {
if VDSO.is_none() {
clock_getres_slow(clockid)
} else {
VDSO.as_ref().unwrap().clock_getres(clockid)
}
}
fn clock_gettime_slow(clockid: ClockId) -> Result<Duration> {
let mut ts = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
cfg_if::cfg_if! {
if #[cfg(feature = "sgx")] {
extern "C" {
fn vdso_ocall_clock_gettime(
ret: *mut libc::c_int,
clockid: libc::c_int,
ts: *mut libc::timespec,
) -> sgx_types::sgx_status_t;
}
let mut ret: libc::c_int = 0;
unsafe {
vdso_ocall_clock_gettime(&mut ret as *mut _, clockid as _, &mut ts as *mut _);
}
} else {
let ret = unsafe { libc::clock_gettime(clockid as _, &mut ts as *mut _) };
}
}
if ret == 0 {
Ok(Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32))
} else {
return_errno!(EINVAL, "clock_gettime_slow failed")
}
}
fn clock_getres_slow(clockid: ClockId) -> Result<Duration> {
let mut res = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
cfg_if::cfg_if! {
if #[cfg(feature = "sgx")] {
extern "C" {
fn vdso_ocall_clock_getres(
ret: *mut libc::c_int,
clockid: libc::c_int,
res: *mut libc::timespec,
) -> sgx_types::sgx_status_t;
}
let mut ret: libc::c_int = 0;
unsafe {
vdso_ocall_clock_getres(&mut ret as *mut _, clockid as _, &mut res as *mut _);
}
} else {
let ret = unsafe { libc::clock_getres(clockid as _, &mut res as *mut _) };
}
}
if ret == 0 {
Ok(Duration::new(res.tv_sec as u64, res.tv_nsec as u32))
} else {
return_errno!(EINVAL, "clock_getres_slow failed")
}
}
// All unit tests
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use super::*;
use std::thread;
const LOOPS: usize = 3;
const SLEEP_DURATION: u64 = 10;
const HRES_MAX_DIFF_NANOS: u64 = 50_000;
const COARSE_MAX_DIFF_NANOS: u64 = 4_000_000;
#[test]
fn test_clock_gettime() {
test_single_clock_gettime(ClockId::CLOCK_REALTIME_COARSE, COARSE_MAX_DIFF_NANOS);
test_single_clock_gettime(ClockId::CLOCK_MONOTONIC_COARSE, COARSE_MAX_DIFF_NANOS);
test_single_clock_gettime(ClockId::CLOCK_REALTIME, HRES_MAX_DIFF_NANOS);
test_single_clock_gettime(ClockId::CLOCK_MONOTONIC, HRES_MAX_DIFF_NANOS);
test_single_clock_gettime(ClockId::CLOCK_BOOTTIME, HRES_MAX_DIFF_NANOS);
test_single_clock_gettime(ClockId::CLOCK_MONOTONIC_RAW, HRES_MAX_DIFF_NANOS);
}
fn test_single_clock_gettime(clockid: ClockId, max_diff_nanos: u64) {
for _ in 0..LOOPS {
let mut libc_tp = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe { libc::clock_gettime(clockid as _, &mut libc_tp as *mut _) };
let libc_time = Duration::new(libc_tp.tv_sec as u64, libc_tp.tv_nsec as u32);
let vdso_time = clock_gettime(clockid).unwrap();
assert!(vdso_time - libc_time <= Duration::from_nanos(max_diff_nanos));
thread::sleep(Duration::from_millis(SLEEP_DURATION));
}
}
#[test]
fn test_clock_getres() {
test_single_clock_getres(ClockId::CLOCK_REALTIME_COARSE);
test_single_clock_getres(ClockId::CLOCK_MONOTONIC_COARSE);
test_single_clock_getres(ClockId::CLOCK_REALTIME);
test_single_clock_getres(ClockId::CLOCK_MONOTONIC);
test_single_clock_getres(ClockId::CLOCK_BOOTTIME);
test_single_clock_getres(ClockId::CLOCK_MONOTONIC_RAW);
}
fn test_single_clock_getres(clockid: ClockId) {
for _ in 0..LOOPS {
let mut libc_tp = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe { libc::clock_getres(clockid as _, &mut libc_tp as *mut _) };
let res = clock_getres(clockid).unwrap();
assert_eq!(res.as_secs(), libc_tp.tv_sec as u64);
assert_eq!(res.subsec_nanos(), libc_tp.tv_nsec as u32);
}
}
#[test]
fn test_monotonic() {
let mut last_now = Duration::new(0, 0);
for _ in 0..1_000_000 {
let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap();
assert!(now >= last_now);
last_now = now;
}
}
mod logger {
use log::{Level, LevelFilter, Metadata, Record};
#[ctor::ctor]
fn auto_init() {
log::set_logger(&LOGGER)
.map(|()| log::set_max_level(LevelFilter::Trace))
.expect("failed to init the logger");
}
static LOGGER: SimpleLogger = SimpleLogger;
struct SimpleLogger;
impl log::Log for SimpleLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= Level::Trace
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
println!("[{}] {}", record.level(), record.args());
}
}
fn flush(&self) {}
}
}
}

@ -0,0 +1,595 @@
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
pub const PAGE_SIZE: u64 = 4096;
pub const CLOCK_TAI: usize = 11;
pub const VDSO_BASES: usize = CLOCK_TAI + 1;
#[cfg(not(any(arget_arch = "x86", target_arch = "x86_64")))]
compile_error!("Only support x86 or x86_64 architecture now.");
/// Reads the current value of the processors time-stamp counter.
///
/// The processor monotonically increments the time-stamp counter MSR every clock cycle
/// and resets it to 0 whenever the processor is reset.
///
/// The RDTSC instruction is not a serializing instruction. It does not necessarily
/// wait until all previous instructions have been executed before reading the counter.
/// Similarly, subsequent instructions may begin execution before the read operation is performed.
pub fn rdtsc() -> u64 {
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
unsafe { core::arch::x86_64::_rdtsc() as u64 }
} else if #[cfg(target_arch = "x86")] {
unsafe { core::arch::x86::_rdtsc() as u64 }
}
}
}
/// Reads the current value of the processors time-stamp counter.
///
/// The processor monotonically increments the time-stamp counter MSR every clock cycle
/// and resets it to 0 whenever the processor is reset.
/// The RDTSCP instruction waits until all previous instructions have been executed before
/// reading the counter. However, subsequent instructions may begin execution before
/// the read operation is performed.
#[allow(dead_code)]
pub fn rdtscp() -> u64 {
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
let mut aux: u32 = 0;
unsafe { core::arch::x86_64::__rdtscp(&mut aux) as u64 }
} else if #[cfg(target_arch = "x86")] {
let mut aux: u32 = 0;
unsafe { core::arch::x86::__rdtscp(&mut aux) as u64 }
}
}
}
/// Performs a serializing operation on all load-from-memory instructions
/// that were issued prior to this instruction.
///
/// Guarantees that every load instruction that precedes, in program order,
/// is globally visible before any load instruction which follows the fence in program order.
pub fn lfence() {
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
unsafe { core::arch::x86_64::_mm_lfence() }
} else if #[cfg(target_arch = "x86")] {
unsafe { core::arch::x86::_mm_lfence() }
}
}
}
/// Read the current TSC in program order.
///
/// The RDTSC instruction might not be ordered relative to memory access.
/// But an RDTSC immediately after an appropriate barrier appears to be ordered as a normal load.
/// Hence, we could use a barrier before RDTSC to get ordered TSC.
///
/// We also can just use RDTSCP, which is also ordered.
pub fn rdtsc_ordered() -> u64 {
lfence();
rdtsc()
}
/// The timers is divided in 3 sets (HRES, COARSE, RAW) since Linux v5.3.
/// CS_HRES_COARSE refers to the first two and CS_RAW to the third.
#[derive(Debug, Copy, Clone)]
#[allow(non_camel_case_types)]
pub enum ClockSource {
CS_HRES_COARSE = 0,
CS_RAW = 1,
}
#[derive(Debug, Copy, Clone)]
#[allow(non_camel_case_types)]
pub enum VdsoClockMode {
VDSO_CLOCKMODE_NONE = 0,
VDSO_CLOCKMODE_TSC = 1,
VDSO_CLOCKMODE_PVCLOCK = 2,
VDSO_CLOCKMODE_HVCLOCK = 3,
VDSO_CLOCKMODE_TIMENS = i32::MAX as isize,
}
// Struct VdsoDataPtr must impl this trait to unify vdso_data interface of different linux verisons.
pub trait VdsoData {
fn sec(&self, clockid: ClockId) -> Result<u64>;
fn nsec(&self, clockid: ClockId) -> Result<u64>;
fn seq(&self) -> u32;
fn clock_mode(&self) -> i32;
fn cycle_last(&self) -> u64;
fn mask(&self) -> u64;
fn mult(&self) -> u32;
fn shift(&self) -> u32;
fn tz_minuteswest(&self) -> i32;
fn tz_dsttime(&self) -> i32;
fn vdsodata_ptr(vdso_addr: u64) -> *const Self
where
Self: Sized;
}
pub enum VdsoDataPtr {
// === Linux 4.0 - 4.4, 4.7 - 4.11 ===
V4_0(*const vdso_data_v4_0),
// === Linux 4.5 - 4.6, 4.12 - 4.19 ===
V4_5(*const vdso_data_v4_5),
// === Linux 5.0 - 5.2 ===
V5_0(*const vdso_data_v5_0),
// === Linux 5.3 - 5.5 ===
V5_3(*const vdso_data_v5_3),
// === Linux 5.6 - 5.8 ===
V5_6(*const vdso_data_v5_6),
// === Linux 5.9 - 6.2 ===
V5_9(*const vdso_data_v5_9),
}
// === Linux 4.0 - 4.4, 4.7 - 4.11 ===
// struct vsyscall_gtod_data
#[repr(C)]
pub struct vdso_data_v4_0 {
pub seq: AtomicU32,
pub vclock_mode: i32,
pub cycle_last: u64,
pub mask: u64,
pub mult: u32,
pub shift: u32,
pub wall_time_snsec: u64,
pub wall_time_sec: u64,
pub monotonic_time_sec: u64,
pub monotonic_time_snsec: u64,
pub wall_time_coarse_sec: u64,
pub wall_time_coarse_nsec: u64,
pub monotonic_time_coarse_sec: u64,
pub monotonic_time_coarse_nsec: u64,
pub tz_minuteswest: i32,
pub tz_dsttime: i32,
}
impl VdsoData for vdso_data_v4_0 {
fn vdsodata_ptr(vdso_addr: u64) -> *const Self {
(vdso_addr - 2 * PAGE_SIZE + 128) as *const Self
}
fn sec(&self, clockid: ClockId) -> Result<u64> {
match clockid {
ClockId::CLOCK_REALTIME => Ok(self.wall_time_sec),
ClockId::CLOCK_MONOTONIC => Ok(self.monotonic_time_sec),
ClockId::CLOCK_REALTIME_COARSE => Ok(self.wall_time_coarse_sec),
ClockId::CLOCK_MONOTONIC_COARSE => Ok(self.monotonic_time_coarse_sec),
_ => return_errno!(EINVAL, "Unsupported clockid in sec()"),
}
}
fn nsec(&self, clockid: ClockId) -> Result<u64> {
match clockid {
ClockId::CLOCK_REALTIME => Ok(self.wall_time_snsec),
ClockId::CLOCK_MONOTONIC => Ok(self.monotonic_time_snsec),
ClockId::CLOCK_REALTIME_COARSE => Ok(self.wall_time_coarse_nsec),
ClockId::CLOCK_MONOTONIC_COARSE => Ok(self.monotonic_time_coarse_nsec),
_ => return_errno!(EINVAL, "Unsupported clockid in nsec()"),
}
}
fn seq(&self) -> u32 {
self.seq.load(Ordering::Relaxed)
}
fn clock_mode(&self) -> i32 {
self.vclock_mode
}
fn cycle_last(&self) -> u64 {
self.cycle_last
}
fn mask(&self) -> u64 {
self.mask
}
fn mult(&self) -> u32 {
self.mult
}
fn shift(&self) -> u32 {
self.shift
}
fn tz_minuteswest(&self) -> i32 {
self.tz_minuteswest
}
fn tz_dsttime(&self) -> i32 {
self.tz_dsttime
}
}
// === Linux 4.5 - 4.6, 4.12 - 4.19 ===
// struct vsyscall_gtod_data
#[repr(C)]
pub struct vdso_data_v4_5 {
pub seq: AtomicU32,
pub vclock_mode: i32,
pub cycle_last: u64,
pub mask: u64,
pub mult: u32,
pub shift: u32,
pub wall_time_snsec: u64,
pub wall_time_sec: u64,
pub monotonic_time_sec: u64,
pub monotonic_time_snsec: u64,
pub wall_time_coarse_sec: u64,
pub wall_time_coarse_nsec: u64,
pub monotonic_time_coarse_sec: u64,
pub monotonic_time_coarse_nsec: u64,
pub tz_minuteswest: i32,
pub tz_dsttime: i32,
}
impl VdsoData for vdso_data_v4_5 {
fn vdsodata_ptr(vdso_addr: u64) -> *const Self {
(vdso_addr - 3 * PAGE_SIZE + 128) as *const Self
}
fn sec(&self, clockid: ClockId) -> Result<u64> {
match clockid {
ClockId::CLOCK_REALTIME => Ok(self.wall_time_sec),
ClockId::CLOCK_MONOTONIC => Ok(self.monotonic_time_sec),
ClockId::CLOCK_REALTIME_COARSE => Ok(self.wall_time_coarse_sec),
ClockId::CLOCK_MONOTONIC_COARSE => Ok(self.monotonic_time_coarse_sec),
_ => return_errno!(EINVAL, "Unsupported clockid in sec()"),
}
}
fn nsec(&self, clockid: ClockId) -> Result<u64> {
match clockid {
ClockId::CLOCK_REALTIME => Ok(self.wall_time_snsec),
ClockId::CLOCK_MONOTONIC => Ok(self.monotonic_time_snsec),
ClockId::CLOCK_REALTIME_COARSE => Ok(self.wall_time_coarse_nsec),
ClockId::CLOCK_MONOTONIC_COARSE => Ok(self.monotonic_time_coarse_nsec),
_ => return_errno!(EINVAL, "Unsupported clockid in nsec()"),
}
}
fn seq(&self) -> u32 {
self.seq.load(Ordering::Relaxed)
}
fn clock_mode(&self) -> i32 {
self.vclock_mode
}
fn cycle_last(&self) -> u64 {
self.cycle_last
}
fn mask(&self) -> u64 {
self.mask
}
fn mult(&self) -> u32 {
self.mult
}
fn shift(&self) -> u32 {
self.shift
}
fn tz_minuteswest(&self) -> i32 {
self.tz_minuteswest
}
fn tz_dsttime(&self) -> i32 {
self.tz_dsttime
}
}
// === Linux 5.0 - 5.2 ===
// struct vsyscall_gtod_data
#[repr(C)]
pub struct vdso_data_v5_0 {
pub seq: AtomicU32,
pub vclock_mode: i32,
pub cycle_last: u64,
pub mask: u64,
pub mult: u32,
pub shift: u32,
pub basetime: [vgtod_ts; VDSO_BASES],
pub tz_minuteswest: i32,
pub tz_dsttime: i32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vgtod_ts {
pub sec: u64,
pub nsec: u64,
}
impl VdsoData for vdso_data_v5_0 {
fn vdsodata_ptr(vdso_addr: u64) -> *const Self {
(vdso_addr - 3 * PAGE_SIZE + 128) as *const Self
}
fn sec(&self, clockid: ClockId) -> Result<u64> {
Ok(self.basetime[clockid as usize].sec)
}
fn nsec(&self, clockid: ClockId) -> Result<u64> {
Ok(self.basetime[clockid as usize].nsec)
}
fn seq(&self) -> u32 {
self.seq.load(Ordering::Relaxed)
}
fn clock_mode(&self) -> i32 {
self.vclock_mode
}
fn cycle_last(&self) -> u64 {
self.cycle_last
}
fn mask(&self) -> u64 {
self.mask
}
fn mult(&self) -> u32 {
self.mult
}
fn shift(&self) -> u32 {
self.shift
}
fn tz_minuteswest(&self) -> i32 {
self.tz_minuteswest
}
fn tz_dsttime(&self) -> i32 {
self.tz_dsttime
}
}
// === Linux 5.3 - 5.5 ===
// struct vdso_data
#[repr(C)]
pub struct vdso_data_v5_3 {
pub seq: AtomicU32,
pub clock_mode: i32,
pub cycle_last: u64,
pub mask: u64,
pub mult: u32,
pub shift: u32,
pub basetime: [vdso_timestamp; VDSO_BASES],
pub tz_minuteswest: i32,
pub tz_dsttime: i32,
pub hrtimer_res: u32,
pub __unused: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vdso_timestamp {
pub sec: u64,
pub nsec: u64,
}
impl VdsoData for vdso_data_v5_3 {
fn vdsodata_ptr(vdso_addr: u64) -> *const Self {
(vdso_addr - 3 * PAGE_SIZE + 128) as *const Self
}
fn sec(&self, clockid: ClockId) -> Result<u64> {
Ok(self.basetime[clockid as usize].sec)
}
fn nsec(&self, clockid: ClockId) -> Result<u64> {
Ok(self.basetime[clockid as usize].nsec)
}
fn seq(&self) -> u32 {
self.seq.load(Ordering::Relaxed)
}
fn clock_mode(&self) -> i32 {
self.clock_mode
}
fn cycle_last(&self) -> u64 {
self.cycle_last
}
fn mask(&self) -> u64 {
self.mask
}
fn mult(&self) -> u32 {
self.mult
}
fn shift(&self) -> u32 {
self.shift
}
fn tz_minuteswest(&self) -> i32 {
self.tz_minuteswest
}
fn tz_dsttime(&self) -> i32 {
self.tz_dsttime
}
}
// === Linux 5.6 - 5.8 ===
// struct vdso_data
#[repr(C)]
pub struct vdso_data_v5_6 {
pub seq: AtomicU32,
pub clock_mode: i32,
pub cycle_last: u64,
pub mask: u64,
pub mult: u32,
pub shift: u32,
pub union_1: vdso_data_v5_6_union_1,
pub tz_minuteswest: i32,
pub tz_dsttime: i32,
pub hrtimer_res: u32,
pub __unused: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct timens_offset {
pub sec: i64,
pub nsec: u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union vdso_data_v5_6_union_1 {
pub basetime: [vdso_timestamp; VDSO_BASES],
pub offset: [timens_offset; VDSO_BASES],
}
impl VdsoData for vdso_data_v5_6 {
fn vdsodata_ptr(vdso_addr: u64) -> *const Self {
(vdso_addr - 4 * PAGE_SIZE + 128) as *const Self
}
fn sec(&self, clockid: ClockId) -> Result<u64> {
unsafe { Ok(self.union_1.basetime[clockid as usize].sec) }
}
fn nsec(&self, clockid: ClockId) -> Result<u64> {
unsafe { Ok(self.union_1.basetime[clockid as usize].nsec) }
}
fn seq(&self) -> u32 {
self.seq.load(Ordering::Relaxed)
}
fn clock_mode(&self) -> i32 {
self.clock_mode
}
fn cycle_last(&self) -> u64 {
self.cycle_last
}
fn mask(&self) -> u64 {
self.mask
}
fn mult(&self) -> u32 {
self.mult
}
fn shift(&self) -> u32 {
self.shift
}
fn tz_minuteswest(&self) -> i32 {
self.tz_minuteswest
}
fn tz_dsttime(&self) -> i32 {
self.tz_dsttime
}
}
// === Linux 5.9 - 5.19, 6.0 - 6.2 ===
// struct vdso_data
#[repr(C)]
pub struct vdso_data_v5_9 {
pub seq: AtomicU32,
pub clock_mode: i32,
pub cycle_last: u64,
pub mask: u64,
pub mult: u32,
pub shift: u32,
pub union_1: vdso_data_v5_6_union_1,
pub tz_minuteswest: i32,
pub tz_dsttime: i32,
pub hrtimer_res: u32,
pub __unused: u32,
pub arch_data: arch_vdso_data,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct arch_vdso_data {}
impl VdsoData for vdso_data_v5_9 {
fn vdsodata_ptr(vdso_addr: u64) -> *const Self {
(vdso_addr - 4 * PAGE_SIZE + 128) as *const Self
}
fn sec(&self, clockid: ClockId) -> Result<u64> {
unsafe { Ok(self.union_1.basetime[clockid as usize].sec) }
}
fn nsec(&self, clockid: ClockId) -> Result<u64> {
unsafe { Ok(self.union_1.basetime[clockid as usize].nsec) }
}
fn seq(&self) -> u32 {
self.seq.load(Ordering::Relaxed)
}
fn clock_mode(&self) -> i32 {
self.clock_mode
}
fn cycle_last(&self) -> u64 {
self.cycle_last
}
fn mask(&self) -> u64 {
self.mask
}
fn mult(&self) -> u32 {
self.mult
}
fn shift(&self) -> u32 {
self.shift
}
fn tz_minuteswest(&self) -> i32 {
self.tz_minuteswest
}
fn tz_dsttime(&self) -> i32 {
self.tz_dsttime
}
}

@ -1,3 +1 @@
use super::*;
pub use errno::{Errno::*, *};

@ -6,7 +6,7 @@ use super::file_ops::{
};
use super::fs_ops;
use super::fs_ops::{MountFlags, MountOptions, UmountFlags};
use super::time::{clockid_t, itimerspec_t, timespec_t, timeval_t, ClockID};
use super::time::{clockid_t, itimerspec_t, timespec_t, timeval_t, ClockId};
use super::timer_file::{TimerCreationFlags, TimerSetFlags};
use super::*;
use crate::config::{user_rootfs_config, ConfigApp, ConfigMountFsType};
@ -42,9 +42,9 @@ pub fn do_eventfd2(init_val: u32, flags: i32) -> Result<isize> {
pub fn do_timerfd_create(clockid: clockid_t, flags: i32) -> Result<isize> {
debug!("timerfd: clockid {}, flags {} ", clockid, flags);
let clockid = ClockID::from_raw(clockid)?;
let clockid = ClockId::try_from(clockid)?;
match clockid {
ClockID::CLOCK_REALTIME | ClockID::CLOCK_MONOTONIC => {}
ClockId::CLOCK_REALTIME | ClockId::CLOCK_MONOTONIC => {}
_ => {
return_errno!(EINVAL, "invalid clockid");
}

@ -1,6 +1,6 @@
use super::*;
use crate::time::{clockid_t, itimerspec_t, timespec_t, ClockID};
use crate::time::{clockid_t, itimerspec_t, timespec_t, ClockId};
use atomic::{Atomic, Ordering};
use std::time::Duration;
@ -13,7 +13,7 @@ pub struct TimerFile {
}
impl TimerFile {
pub fn new(clockid: ClockID, flags: TimerCreationFlags) -> Result<Self> {
pub fn new(clockid: ClockId, flags: TimerCreationFlags) -> Result<Self> {
let raw_host_fd = try_libc!({
let mut ret: i32 = 0;
let status = occlum_ocall_timerfd_create(&mut ret, clockid as clockid_t, flags.bits());

@ -66,6 +66,7 @@ extern crate intrusive_collections;
extern crate itertools;
extern crate modular_bitfield;
extern crate resolv_conf;
extern crate vdso_time;
use sgx_trts::libc;
use sgx_types::*;

@ -102,12 +102,12 @@ pub fn do_exec(
// Blocking wait until there is only one thread in the calling process
fn wait_for_other_threads_to_exit(current_ref: &ThreadRef) {
use super::do_futex::{self, FutexTimeout};
use crate::time::{timespec_t, ClockID};
use crate::time::{timespec_t, ClockId};
use core::time::Duration;
// Set timeout to 50ms
let timeout = FutexTimeout::new(
ClockID::CLOCK_MONOTONIC,
ClockId::CLOCK_MONOTONIC,
timespec_t::from(Duration::from_millis(50)),
false,
);

@ -4,7 +4,7 @@ use std::intrinsics::atomic_load_seqcst;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::prelude::*;
use crate::time::{timespec_t, ClockID};
use crate::time::{timespec_t, ClockId};
/// `FutexOp`, `FutexFlags`, and `futex_op_and_flags_from_u32` are helper types and
/// functions for handling the versatile commands and arguments of futex system
@ -75,13 +75,13 @@ const FUTEX_BITSET_MATCH_ANY: u32 = 0xFFFF_FFFF;
#[derive(Debug, Copy, Clone)]
pub struct FutexTimeout {
clock_id: ClockID,
clock_id: ClockId,
ts: timespec_t,
absolute_time: bool,
}
impl FutexTimeout {
pub fn new(clock_id: ClockID, ts: timespec_t, absolute_time: bool) -> Self {
pub fn new(clock_id: ClockId, ts: timespec_t, absolute_time: bool) -> Self {
Self {
clock_id,
ts,
@ -89,7 +89,7 @@ impl FutexTimeout {
}
}
pub fn clock_id(&self) -> &ClockID {
pub fn clock_id(&self) -> &ClockId {
&self.clock_id
}
@ -502,7 +502,7 @@ fn wait_event_timeout(thread: *const c_void, timeout: &Option<FutexTimeout>) ->
.as_ref()
.map(|timeout| {
let clockbit = match timeout.clock_id() {
ClockID::CLOCK_REALTIME => FutexFlags::FUTEX_CLOCK_REALTIME.bits() as i32,
ClockId::CLOCK_REALTIME => FutexFlags::FUTEX_CLOCK_REALTIME.bits() as i32,
_ => 0,
};
(

@ -11,7 +11,7 @@ use super::process::ProcessFilter;
use super::spawn_attribute::{clone_spawn_atrributes_safely, posix_spawnattr_t, SpawnAttr};
use crate::prelude::*;
use crate::syscall::CpuContext;
use crate::time::{timespec_t, ClockID};
use crate::time::{timespec_t, ClockId};
use crate::util::mem_util::from_user::*;
use std::ptr::NonNull;
@ -290,9 +290,9 @@ pub fn do_futex(
"FUTEX_CLOCK_REALTIME with this futex operation is not supported"
);
}
ClockID::CLOCK_REALTIME
ClockId::CLOCK_REALTIME
} else {
ClockID::CLOCK_MONOTONIC
ClockId::CLOCK_MONOTONIC
};
// From futex man page:

@ -932,7 +932,7 @@ fn do_gettimeofday(tv_u: *mut timeval_t) -> Result<isize> {
fn do_clock_gettime(clockid: clockid_t, ts_u: *mut timespec_t) -> Result<isize> {
check_mut_ptr(ts_u)?;
let clockid = time::ClockID::from_raw(clockid)?;
let clockid = time::ClockId::try_from(clockid)?;
let ts = time::do_clock_gettime(clockid)?;
unsafe {
*ts_u = ts;
@ -941,7 +941,7 @@ fn do_clock_gettime(clockid: clockid_t, ts_u: *mut timespec_t) -> Result<isize>
}
fn do_time(tloc_u: *mut time_t) -> Result<isize> {
let ts = time::do_clock_gettime(time::ClockID::CLOCK_REALTIME)?;
let ts = time::do_clock_gettime(time::ClockId::CLOCK_REALTIME)?;
if !tloc_u.is_null() {
check_mut_ptr(tloc_u)?;
unsafe {
@ -952,7 +952,7 @@ fn do_time(tloc_u: *mut time_t) -> Result<isize> {
}
fn do_clock_getres(clockid: clockid_t, res_u: *mut timespec_t) -> Result<isize> {
let clockid = time::ClockID::from_raw(clockid)?;
let clockid = time::ClockId::try_from(clockid)?;
if res_u.is_null() {
return Ok(0);
}
@ -981,7 +981,7 @@ fn do_clock_nanosleep(
} else {
None
};
let clockid = time::ClockID::from_raw(clockid)?;
let clockid = time::ClockId::try_from(clockid)?;
time::do_clock_nanosleep(clockid, flags, &req, rem)?;
Ok(0)
}

@ -14,6 +14,7 @@ pub mod up_time;
pub use profiler::ThreadProfiler;
pub use timer_slack::TIMERSLACK;
pub use vdso_time::ClockId;
#[allow(non_camel_case_types)]
pub type time_t = i64;
@ -74,15 +75,9 @@ impl From<Duration> for timeval_t {
}
pub fn do_gettimeofday() -> timeval_t {
extern "C" {
fn occlum_ocall_gettimeofday(tv: *mut timeval_t) -> sgx_status_t;
}
let mut tv: timeval_t = Default::default();
unsafe {
occlum_ocall_gettimeofday(&mut tv as *mut timeval_t);
}
tv.validate().expect("ocall returned invalid timeval_t");
let tv = timeval_t::from(vdso_time::clock_gettime(ClockId::CLOCK_REALTIME).unwrap());
tv.validate()
.expect("gettimeofday returned invalid timeval_t");
tv
}
@ -149,58 +144,15 @@ impl timespec_t {
#[allow(non_camel_case_types)]
pub type clockid_t = i32;
#[derive(Debug, Copy, Clone)]
#[allow(non_camel_case_types)]
pub enum ClockID {
CLOCK_REALTIME = 0,
CLOCK_MONOTONIC = 1,
CLOCK_PROCESS_CPUTIME_ID = 2,
CLOCK_THREAD_CPUTIME_ID = 3,
CLOCK_MONOTONIC_RAW = 4,
CLOCK_REALTIME_COARSE = 5,
CLOCK_MONOTONIC_COARSE = 6,
CLOCK_BOOTTIME = 7,
}
impl ClockID {
#[deny(unreachable_patterns)]
pub fn from_raw(clockid: clockid_t) -> Result<ClockID> {
Ok(match clockid as i32 {
0 => ClockID::CLOCK_REALTIME,
1 => ClockID::CLOCK_MONOTONIC,
2 => ClockID::CLOCK_PROCESS_CPUTIME_ID,
3 => ClockID::CLOCK_THREAD_CPUTIME_ID,
4 => ClockID::CLOCK_MONOTONIC_RAW,
5 => ClockID::CLOCK_REALTIME_COARSE,
6 => ClockID::CLOCK_MONOTONIC_COARSE,
7 => ClockID::CLOCK_BOOTTIME,
_ => return_errno!(EINVAL, "invalid command"),
})
}
}
pub fn do_clock_gettime(clockid: ClockID) -> Result<timespec_t> {
extern "C" {
fn occlum_ocall_clock_gettime(clockid: clockid_t, tp: *mut timespec_t) -> sgx_status_t;
}
let mut tv: timespec_t = Default::default();
unsafe {
occlum_ocall_clock_gettime(clockid as clockid_t, &mut tv as *mut timespec_t);
}
tv.validate().expect("ocall returned invalid timespec");
pub fn do_clock_gettime(clockid: ClockId) -> Result<timespec_t> {
let tv = timespec_t::from(vdso_time::clock_gettime(clockid).unwrap());
tv.validate()
.expect("clock_gettime returned invalid timespec");
Ok(tv)
}
pub fn do_clock_getres(clockid: ClockID) -> Result<timespec_t> {
extern "C" {
fn occlum_ocall_clock_getres(clockid: clockid_t, res: *mut timespec_t) -> sgx_status_t;
}
let mut res: timespec_t = Default::default();
unsafe {
occlum_ocall_clock_getres(clockid as clockid_t, &mut res as *mut timespec_t);
}
pub fn do_clock_getres(clockid: ClockId) -> Result<timespec_t> {
let res = timespec_t::from(vdso_time::clock_getres(clockid).unwrap());
let validate_resolution = |res: &timespec_t| -> Result<()> {
// The resolution can be ranged from 1 nanosecond to a few milliseconds
if res.sec == 0 && res.nsec > 0 && res.nsec < 1_000_000_000 {
@ -210,14 +162,14 @@ pub fn do_clock_getres(clockid: ClockID) -> Result<timespec_t> {
}
};
// do sanity check
validate_resolution(&res).expect("ocall returned invalid resolution");
validate_resolution(&res).expect("clock_getres returned invalid resolution");
Ok(res)
}
const TIMER_ABSTIME: i32 = 0x01;
pub fn do_clock_nanosleep(
clockid: ClockID,
clockid: ClockId,
flags: i32,
req: &timespec_t,
rem: Option<&mut timespec_t>,
@ -235,11 +187,11 @@ pub fn do_clock_nanosleep(
let mut ret = 0;
let mut u_rem: timespec_t = timespec_t { sec: 0, nsec: 0 };
match clockid {
ClockID::CLOCK_REALTIME
| ClockID::CLOCK_MONOTONIC
| ClockID::CLOCK_BOOTTIME
| ClockID::CLOCK_PROCESS_CPUTIME_ID => {}
ClockID::CLOCK_THREAD_CPUTIME_ID => {
ClockId::CLOCK_REALTIME
| ClockId::CLOCK_MONOTONIC
| ClockId::CLOCK_BOOTTIME
| ClockId::CLOCK_PROCESS_CPUTIME_ID => {}
ClockId::CLOCK_THREAD_CPUTIME_ID => {
return_errno!(EINVAL, "CLOCK_THREAD_CPUTIME_ID is not a permitted value");
}
_ => {
@ -269,7 +221,7 @@ pub fn do_nanosleep(req: &timespec_t, rem: Option<&mut timespec_t>) -> Result<()
// the CLOCK_REALTIME clock. However, Linux measures the time using
// the CLOCK_MONOTONIC clock.
// Here we follow the POSIX.1
let clock_id = ClockID::CLOCK_REALTIME;
let clock_id = ClockId::CLOCK_REALTIME;
return do_clock_nanosleep(clock_id, 0, req, rem);
}

@ -1,11 +1,11 @@
use super::{do_clock_gettime, ClockID};
use super::{do_clock_gettime, ClockId};
use std::time::Duration;
lazy_static! {
static ref BOOT_TIME_STAMP: Duration = do_clock_gettime(ClockID::CLOCK_MONOTONIC_RAW)
static ref BOOT_TIME_STAMP: Duration = do_clock_gettime(ClockId::CLOCK_MONOTONIC_RAW)
.unwrap()
.as_duration();
static ref BOOT_TIME_STAMP_SINCE_EPOCH: Duration = do_clock_gettime(ClockID::CLOCK_REALTIME)
static ref BOOT_TIME_STAMP_SINCE_EPOCH: Duration = do_clock_gettime(ClockId::CLOCK_REALTIME)
.unwrap()
.as_duration();
}
@ -20,7 +20,7 @@ pub fn boot_time_since_epoch() -> Duration {
}
pub fn get() -> Option<Duration> {
do_clock_gettime(ClockID::CLOCK_MONOTONIC_RAW)
do_clock_gettime(ClockId::CLOCK_MONOTONIC_RAW)
.unwrap()
.as_duration()
.checked_sub(*BOOT_TIME_STAMP)

@ -21,6 +21,8 @@ C_SRCS := $(sort $(wildcard src/*.c src/*/*.c))
CXX_SRCS := $(sort $(wildcard src/*.cpp src/*/*.cpp))
C_OBJS := $(addprefix $(OBJ_DIR)/pal/,$(C_SRCS:.c=.o))
CXX_OBJS := $(addprefix $(OBJ_DIR)/pal/,$(CXX_SRCS:.cpp=.o))
VDSO_SRCS := $(CRATES_DIR)/vdso-time/ocalls/vdso-time-ocalls.c
VDSO_OBJS := $(addprefix $(OBJ_DIR)/pal/external_ocalls,$(VDSO_SRCS:.c=.o))
# Object files for simulation mode are stored in pal/src_sim
ifeq ($(SGX_MODE), SIM)
@ -52,7 +54,7 @@ LINK_FLAGS += -lsgx_quote_ex_sim
endif
endif
ALL_BUILD_SUBDIRS := $(sort $(patsubst %/,%,$(dir $(LIBOCCLUM_PAL_SO_REAL) $(EDL_C_OBJS) $(C_OBJS) $(CXX_OBJS))))
ALL_BUILD_SUBDIRS := $(sort $(patsubst %/,%,$(dir $(LIBOCCLUM_PAL_SO_REAL) $(EDL_C_OBJS) $(C_OBJS) $(CXX_OBJS) $(VDSO_OBJS))))
.PHONY: all format format-check clean
@ -61,7 +63,7 @@ all: $(ALL_BUILD_SUBDIRS) $(LIBOCCLUM_PAL_SO_REAL)
$(ALL_BUILD_SUBDIRS):
@mkdir -p $@
$(LIBOCCLUM_PAL_SO_REAL): $(LIBSGX_USTDC_A) $(EDL_C_OBJS) $(C_OBJS) $(CXX_OBJS)
$(LIBOCCLUM_PAL_SO_REAL): $(LIBSGX_USTDC_A) $(EDL_C_OBJS) $(C_OBJS) $(CXX_OBJS) $(VDSO_OBJS)
@$(CXX) $^ -o $@ $(LINK_FLAGS) -Wl,-soname=$(LIBOCCLUM_PAL_SONAME)
@# Create symbolic files because occlum run and exec will need it when linking.
@cd $(BUILD_DIR)/lib && ln -sf $(notdir $(LIBOCCLUM_PAL_SO_REAL)) $(notdir $(LIBOCCLUM_PAL_SONAME)) && \
@ -76,7 +78,8 @@ $(OBJ_DIR)/pal/$(SRC_OBJ)/Enclave_u.c: $(SGX_EDGER8R) ../Enclave.edl
@cd $(OBJ_DIR)/pal/$(SRC_OBJ) && \
$(SGX_EDGER8R) $(SGX_EDGER8R_MODE) --untrusted $(CUR_DIR)/../Enclave.edl \
--search-path $(SGX_SDK)/include \
--search-path $(RUST_SGX_SDK_DIR)/edl/
--search-path $(RUST_SGX_SDK_DIR)/edl/ \
--search-path $(CRATES_DIR)/vdso-time/ocalls
@echo "GEN <= $@"
$(OBJ_DIR)/pal/$(SRC_OBJ)/%.o: src/%.c
@ -87,6 +90,10 @@ $(OBJ_DIR)/pal/$(SRC_OBJ)/%.o: src/%.cpp
@$(CXX) $(CXX_FLAGS) -c $< -o $@
@echo "CXX <= $@"
$(VDSO_OBJS): $(VDSO_SRCS)
@$(CC) $(C_FLAGS) -c $< -o $@
@echo "CC <= $@"
$(LIBSGX_USTDC_A):
@$(MAKE) --no-print-directory -C $(RUST_SGX_SDK_DIR)/sgx_ustdc/ > /dev/null
@cp $(RUST_SGX_SDK_DIR)/sgx_ustdc/libsgx_ustdc.a $(LIBSGX_USTDC_A)

@ -4,18 +4,6 @@
#include <sys/prctl.h>
#include "ocalls.h"
void occlum_ocall_gettimeofday(struct timeval *tv) {
gettimeofday(tv, NULL);
}
void occlum_ocall_clock_gettime(int clockid, struct timespec *tp) {
clock_gettime(clockid, tp);
}
void occlum_ocall_clock_getres(int clockid, struct timespec *res) {
clock_getres(clockid, res);
}
int occlum_ocall_clock_nanosleep(clockid_t clockid, int flags, const struct timespec *req,
struct timespec *rem) {
return clock_nanosleep(clockid, flags, req, rem);

@ -2,6 +2,7 @@ MAIN_MAKEFILE := $(firstword $(MAKEFILE_LIST))
INCLUDE_MAKEFILE := $(lastword $(MAKEFILE_LIST))
CUR_DIR := $(shell dirname $(realpath $(MAIN_MAKEFILE)))
PROJECT_DIR := $(realpath $(CUR_DIR)/../../)
CRATES_DIR := $(realpath $(CUR_DIR)/../libos/crates)
SHELL := /bin/bash