[RFC,v2,3/3] selftests/x86: Augment SGX selftest to test new __vdso_sgx_enter_enclave() and its callback interface
diff mbox series

Message ID 20190424062623.4345-4-cedric.xing@intel.com
State New
Headers show
Series
  • An alternative __vdso_sgx_enter_enclave() to allow enclave/host parameter passing using untrusted stack
Related show

Commit Message

Xing, Cedric April 24, 2019, 6:26 a.m. UTC
This patch augments SGX selftest with two new tests.

The first test exercises the newly added callback interface, by marking the
whole enclave range as PROT_READ, then calling mprotect() upon #PFs to add
necessary PTE permissions per PFEC (#PF Error Code) until the enclave finishes.
This test also serves as an example to demonstrate the callback interface.

The second test single-steps through __vdso_sgx_enter_enclave() to make sure
the call stack can be unwound at every instruction within that vDSO API. Its
purpose is to validate the hand-crafted CFI directives in the assembly.

Signed-off-by: Cedric Xing <cedric.xing@intel.com>
---
 tools/testing/selftests/x86/sgx/Makefile   |   6 +-
 tools/testing/selftests/x86/sgx/main.c     | 323 ++++++++++++++++++---
 tools/testing/selftests/x86/sgx/sgx_call.S |  40 ++-
 3 files changed, 322 insertions(+), 47 deletions(-)

Comments

Jarkko Sakkinen July 12, 2019, 3:25 a.m. UTC | #1
On Tue, Apr 23, 2019 at 11:26:23PM -0700, Cedric Xing wrote:
> This patch augments SGX selftest with two new tests.
> 
> The first test exercises the newly added callback interface, by marking the
> whole enclave range as PROT_READ, then calling mprotect() upon #PFs to add
> necessary PTE permissions per PFEC (#PF Error Code) until the enclave finishes.
> This test also serves as an example to demonstrate the callback interface.
> 
> The second test single-steps through __vdso_sgx_enter_enclave() to make sure
> the call stack can be unwound at every instruction within that vDSO API. Its
> purpose is to validate the hand-crafted CFI directives in the assembly.
> 
> Signed-off-by: Cedric Xing <cedric.xing@intel.com>
> ---
>  tools/testing/selftests/x86/sgx/Makefile   |   6 +-
>  tools/testing/selftests/x86/sgx/main.c     | 323 ++++++++++++++++++---
>  tools/testing/selftests/x86/sgx/sgx_call.S |  40 ++-
>  3 files changed, 322 insertions(+), 47 deletions(-)
> 
> diff --git a/tools/testing/selftests/x86/sgx/Makefile b/tools/testing/selftests/x86/sgx/Makefile
> index 3af15d7c8644..31f937e220c4 100644
> --- a/tools/testing/selftests/x86/sgx/Makefile
> +++ b/tools/testing/selftests/x86/sgx/Makefile
> @@ -14,16 +14,16 @@ TEST_CUSTOM_PROGS := $(OUTPUT)/test_sgx
>  all_64: $(TEST_CUSTOM_PROGS)
>  
>  $(TEST_CUSTOM_PROGS): main.c sgx_call.S $(OUTPUT)/encl_piggy.o
> -	$(CC) $(HOST_CFLAGS) -o $@ $^
> +	$(CC) $(HOST_CFLAGS) -o $@ $^ -lunwind -ldl -Wl,--defsym,__image_base=0 -pie
>  
>  $(OUTPUT)/encl_piggy.o: encl_piggy.S $(OUTPUT)/encl.bin $(OUTPUT)/encl.ss
>  	$(CC) $(HOST_CFLAGS) -I$(OUTPUT) -c $< -o $@
>  
>  $(OUTPUT)/encl.bin: $(OUTPUT)/encl.elf
> -	objcopy --remove-section=.got.plt -O binary $< $@
> +	objcopy -O binary $< $@
>  
>  $(OUTPUT)/encl.elf: encl.lds encl.c encl_bootstrap.S
> -	$(CC) $(ENCL_CFLAGS) -T $^ -o $@
> +	$(CC) $(ENCL_CFLAGS) -T $^ -o $@ -Wl,--build-id=none
>  
>  $(OUTPUT)/encl.ss: $(OUTPUT)/sgxsign signing_key.pem $(OUTPUT)/encl.bin
>  	$^ $@
> diff --git a/tools/testing/selftests/x86/sgx/main.c b/tools/testing/selftests/x86/sgx/main.c
> index e2265f841fb0..d3e53c71306d 100644
> --- a/tools/testing/selftests/x86/sgx/main.c
> +++ b/tools/testing/selftests/x86/sgx/main.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
>  // Copyright(c) 2016-18 Intel Corporation.
>  
> +#define _GNU_SOURCE
>  #include <elf.h>
>  #include <fcntl.h>
>  #include <stdbool.h>
> @@ -9,16 +10,31 @@
>  #include <stdlib.h>
>  #include <string.h>
>  #include <unistd.h>
> +#include <errno.h>
>  #include <sys/ioctl.h>
>  #include <sys/mman.h>
>  #include <sys/stat.h>
> -#include <sys/time.h>
> +#include <sys/auxv.h>
> +#include <signal.h>
> +#include <sys/ucontext.h>
> +
> +#define UNW_LOCAL_ONLY
> +#include <libunwind.h>
> +
>  #include "encl_piggy.h"
>  #include "defines.h"
>  #include "../../../../../arch/x86/kernel/cpu/sgx/arch.h"
>  #include "../../../../../arch/x86/include/uapi/asm/sgx.h"
>  
> -static const uint64_t MAGIC = 0x1122334455667788ULL;
> +#define _Q(x)	__Q(x)
> +#define __Q(x)	#x
> +#define ERRLN	"Line " _Q(__LINE__)
> +
> +#define X86_EFLAGS_TF	(1ul << 8)
> +
> +extern char __image_base[];
> +size_t eenter;
> +static size_t vdso_base;
>  
>  struct vdso_symtab {
>  	Elf64_Sym *elf_symtab;
> @@ -26,20 +42,11 @@ struct vdso_symtab {
>  	Elf64_Word *elf_hashtab;
>  };
>  
> -static void *vdso_get_base_addr(char *envp[])
> +static void vdso_init(void)
>  {
> -	Elf64_auxv_t *auxv;
> -	int i;
> -
> -	for (i = 0; envp[i]; i++);
> -	auxv = (Elf64_auxv_t *)&envp[i + 1];
> -
> -	for (i = 0; auxv[i].a_type != AT_NULL; i++) {
> -		if (auxv[i].a_type == AT_SYSINFO_EHDR)
> -			return (void *)auxv[i].a_un.a_val;
> -	}
> -
> -	return NULL;
> +	vdso_base = getauxval(AT_SYSINFO_EHDR);
> +	if (!vdso_base)
> +		exit(1);
>  }

The clean up makes sense but should be a separate patch i.e. one
logical change per patch. Right now the patch does other mods
than the ones explcitly stated in the commit message.

I'd suggest open coding vdso_init() to the call site in that
patch.

Please try to always minimize for diff's.

>  
>  static Elf64_Dyn *vdso_get_dyntab(void *addr)
> @@ -66,8 +73,9 @@ static void *vdso_get_dyn(void *addr, Elf64_Dyn *dyntab, Elf64_Sxword tag)
>  	return NULL;
>  }
>  
> -static bool vdso_get_symtab(void *addr, struct vdso_symtab *symtab)
> +static bool vdso_get_symtab(struct vdso_symtab *symtab)
>  {
> +	void *addr = (void *)vdso_base;
>  	Elf64_Dyn *dyntab = vdso_get_dyntab(addr);
>  
>  	symtab->elf_symtab = vdso_get_dyn(addr, dyntab, DT_SYMTAB);
> @@ -138,7 +146,7 @@ static bool encl_create(int dev_fd, unsigned long bin_size,
>  	base = mmap(NULL, secs->size, PROT_READ | PROT_WRITE | PROT_EXEC,
>  		    MAP_SHARED, dev_fd, 0);
>  	if (base == MAP_FAILED) {
> -		perror("mmap");
> +		perror(ERRLN);
>  		return false;
>  	}
>  
> @@ -224,35 +232,271 @@ static bool encl_load(struct sgx_secs *secs, unsigned long bin_size)
>  	return false;
>  }
>  
> -void sgx_call(void *rdi, void *rsi, void *tcs,
> -	      struct sgx_enclave_exception *exception,
> -	      void *eenter);
> +int sgx_call(void *rdi, void *rsi, long rdx, void *rcx, void *r8, void *r9,
> +	     void *tcs, struct sgx_enclave_exinfo *ei, void *cb);
> +
> +static void show_enclave_exinfo(const struct sgx_enclave_exinfo *exinfop,
> +				const char *header)
> +{
> +	static const char * const enclu_leaves[] = {
> +		"EREPORT",
> +		"EGETKEY",
> +		"EENTER",
> +		"ERESUME",
> +		"EEXIT"
> +	};
> +	static const char * const exception_names[] = {
> +		"#DE",
> +		"#DB",
> +		"NMI",
> +		"#BP",
> +		"#OF",
> +		"#BR",
> +		"#UD",
> +		"#NM",
> +		"#DF",
> +		"CSO",
> +		"#TS",
> +		"#NP",
> +		"#SS",
> +		"#GP",
> +		"#PF",
> +		"Unknown",
> +		"#MF",
> +		"#AC",
> +		"#MC",
> +		"#XM",
> +		"#VE",
> +		"Unknown",
> +		"Unknown",
> +		"Unknown",
> +		"Unknown",
> +		"Unknown",
> +		"Unknown",
> +		"Unknown",
> +		"Unknown",
> +		"Unknown",
> +		"Unknown",
> +		"Unknown"
> +	};
> +
> +	printf("%s: leaf:%s(%d)", header,
> +		enclu_leaves[exinfop->leaf], exinfop->leaf);
> +	if (exinfop->leaf != 4)
> +		printf(" trap:%s(%d) ec:%d addr:0x%llx\n",
> +			exception_names[exinfop->trapnr], exinfop->trapnr,
> +			exinfop->error_code, exinfop->address);
> +	else
> +		printf("\n");
> +}
> +
> +static const uint64_t MAGIC = 0x1122334455667788ULL;
>  
> -int main(int argc, char *argv[], char *envp[])
> +static void test1(struct sgx_secs *secs)

test1, test2 and test3 are not too descriptive names. Every patch should
make the code base cleaner, not messier.

/Jarkko
Xing, Cedric July 13, 2019, 7:03 a.m. UTC | #2
On 7/11/2019 8:25 PM, Jarkko Sakkinen wrote:
> On Tue, Apr 23, 2019 at 11:26:23PM -0700, Cedric Xing wrote:
>> This patch augments SGX selftest with two new tests.
>>
>> The first test exercises the newly added callback interface, by marking the
>> whole enclave range as PROT_READ, then calling mprotect() upon #PFs to add
>> necessary PTE permissions per PFEC (#PF Error Code) until the enclave finishes.
>> This test also serves as an example to demonstrate the callback interface.
>>
>> The second test single-steps through __vdso_sgx_enter_enclave() to make sure
>> the call stack can be unwound at every instruction within that vDSO API. Its
>> purpose is to validate the hand-crafted CFI directives in the assembly.
>>
>> Signed-off-by: Cedric Xing <cedric.xing@intel.com>
>> ---
>>   tools/testing/selftests/x86/sgx/Makefile   |   6 +-
>>   tools/testing/selftests/x86/sgx/main.c     | 323 ++++++++++++++++++---
>>   tools/testing/selftests/x86/sgx/sgx_call.S |  40 ++-
>>   3 files changed, 322 insertions(+), 47 deletions(-)
>>
>> diff --git a/tools/testing/selftests/x86/sgx/Makefile b/tools/testing/selftests/x86/sgx/Makefile
>> index 3af15d7c8644..31f937e220c4 100644
>> --- a/tools/testing/selftests/x86/sgx/Makefile
>> +++ b/tools/testing/selftests/x86/sgx/Makefile
>> @@ -14,16 +14,16 @@ TEST_CUSTOM_PROGS := $(OUTPUT)/test_sgx
>>   all_64: $(TEST_CUSTOM_PROGS)
>>   
>>   $(TEST_CUSTOM_PROGS): main.c sgx_call.S $(OUTPUT)/encl_piggy.o
>> -	$(CC) $(HOST_CFLAGS) -o $@ $^
>> +	$(CC) $(HOST_CFLAGS) -o $@ $^ -lunwind -ldl -Wl,--defsym,__image_base=0 -pie
>>   
>>   $(OUTPUT)/encl_piggy.o: encl_piggy.S $(OUTPUT)/encl.bin $(OUTPUT)/encl.ss
>>   	$(CC) $(HOST_CFLAGS) -I$(OUTPUT) -c $< -o $@
>>   
>>   $(OUTPUT)/encl.bin: $(OUTPUT)/encl.elf
>> -	objcopy --remove-section=.got.plt -O binary $< $@
>> +	objcopy -O binary $< $@
>>   
>>   $(OUTPUT)/encl.elf: encl.lds encl.c encl_bootstrap.S
>> -	$(CC) $(ENCL_CFLAGS) -T $^ -o $@
>> +	$(CC) $(ENCL_CFLAGS) -T $^ -o $@ -Wl,--build-id=none
>>   
>>   $(OUTPUT)/encl.ss: $(OUTPUT)/sgxsign signing_key.pem $(OUTPUT)/encl.bin
>>   	$^ $@
>> diff --git a/tools/testing/selftests/x86/sgx/main.c b/tools/testing/selftests/x86/sgx/main.c
>> index e2265f841fb0..d3e53c71306d 100644
>> --- a/tools/testing/selftests/x86/sgx/main.c
>> +++ b/tools/testing/selftests/x86/sgx/main.c
>> @@ -1,6 +1,7 @@
>>   // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
>>   // Copyright(c) 2016-18 Intel Corporation.
>>   
>> +#define _GNU_SOURCE
>>   #include <elf.h>
>>   #include <fcntl.h>
>>   #include <stdbool.h>
>> @@ -9,16 +10,31 @@
>>   #include <stdlib.h>
>>   #include <string.h>
>>   #include <unistd.h>
>> +#include <errno.h>
>>   #include <sys/ioctl.h>
>>   #include <sys/mman.h>
>>   #include <sys/stat.h>
>> -#include <sys/time.h>
>> +#include <sys/auxv.h>
>> +#include <signal.h>
>> +#include <sys/ucontext.h>
>> +
>> +#define UNW_LOCAL_ONLY
>> +#include <libunwind.h>
>> +
>>   #include "encl_piggy.h"
>>   #include "defines.h"
>>   #include "../../../../../arch/x86/kernel/cpu/sgx/arch.h"
>>   #include "../../../../../arch/x86/include/uapi/asm/sgx.h"
>>   
>> -static const uint64_t MAGIC = 0x1122334455667788ULL;
>> +#define _Q(x)	__Q(x)
>> +#define __Q(x)	#x
>> +#define ERRLN	"Line " _Q(__LINE__)
>> +
>> +#define X86_EFLAGS_TF	(1ul << 8)
>> +
>> +extern char __image_base[];
>> +size_t eenter;
>> +static size_t vdso_base;
>>   
>>   struct vdso_symtab {
>>   	Elf64_Sym *elf_symtab;
>> @@ -26,20 +42,11 @@ struct vdso_symtab {
>>   	Elf64_Word *elf_hashtab;
>>   };
>>   
>> -static void *vdso_get_base_addr(char *envp[])
>> +static void vdso_init(void)
>>   {
>> -	Elf64_auxv_t *auxv;
>> -	int i;
>> -
>> -	for (i = 0; envp[i]; i++);
>> -	auxv = (Elf64_auxv_t *)&envp[i + 1];
>> -
>> -	for (i = 0; auxv[i].a_type != AT_NULL; i++) {
>> -		if (auxv[i].a_type == AT_SYSINFO_EHDR)
>> -			return (void *)auxv[i].a_un.a_val;
>> -	}
>> -
>> -	return NULL;
>> +	vdso_base = getauxval(AT_SYSINFO_EHDR);
>> +	if (!vdso_base)
>> +		exit(1);
>>   }
> 
> The clean up makes sense but should be a separate patch i.e. one
> logical change per patch. Right now the patch does other mods
> than the ones explcitly stated in the commit message.
> 
> I'd suggest open coding vdso_init() to the call site in that
> patch.
> 
> Please try to always minimize for diff's.
> 
>>   
>>   static Elf64_Dyn *vdso_get_dyntab(void *addr)
>> @@ -66,8 +73,9 @@ static void *vdso_get_dyn(void *addr, Elf64_Dyn *dyntab, Elf64_Sxword tag)
>>   	return NULL;
>>   }
>>   
>> -static bool vdso_get_symtab(void *addr, struct vdso_symtab *symtab)
>> +static bool vdso_get_symtab(struct vdso_symtab *symtab)
>>   {
>> +	void *addr = (void *)vdso_base;
>>   	Elf64_Dyn *dyntab = vdso_get_dyntab(addr);
>>   
>>   	symtab->elf_symtab = vdso_get_dyn(addr, dyntab, DT_SYMTAB);
>> @@ -138,7 +146,7 @@ static bool encl_create(int dev_fd, unsigned long bin_size,
>>   	base = mmap(NULL, secs->size, PROT_READ | PROT_WRITE | PROT_EXEC,
>>   		    MAP_SHARED, dev_fd, 0);
>>   	if (base == MAP_FAILED) {
>> -		perror("mmap");
>> +		perror(ERRLN);
>>   		return false;
>>   	}
>>   
>> @@ -224,35 +232,271 @@ static bool encl_load(struct sgx_secs *secs, unsigned long bin_size)
>>   	return false;
>>   }
>>   
>> -void sgx_call(void *rdi, void *rsi, void *tcs,
>> -	      struct sgx_enclave_exception *exception,
>> -	      void *eenter);
>> +int sgx_call(void *rdi, void *rsi, long rdx, void *rcx, void *r8, void *r9,
>> +	     void *tcs, struct sgx_enclave_exinfo *ei, void *cb);
>> +
>> +static void show_enclave_exinfo(const struct sgx_enclave_exinfo *exinfop,
>> +				const char *header)
>> +{
>> +	static const char * const enclu_leaves[] = {
>> +		"EREPORT",
>> +		"EGETKEY",
>> +		"EENTER",
>> +		"ERESUME",
>> +		"EEXIT"
>> +	};
>> +	static const char * const exception_names[] = {
>> +		"#DE",
>> +		"#DB",
>> +		"NMI",
>> +		"#BP",
>> +		"#OF",
>> +		"#BR",
>> +		"#UD",
>> +		"#NM",
>> +		"#DF",
>> +		"CSO",
>> +		"#TS",
>> +		"#NP",
>> +		"#SS",
>> +		"#GP",
>> +		"#PF",
>> +		"Unknown",
>> +		"#MF",
>> +		"#AC",
>> +		"#MC",
>> +		"#XM",
>> +		"#VE",
>> +		"Unknown",
>> +		"Unknown",
>> +		"Unknown",
>> +		"Unknown",
>> +		"Unknown",
>> +		"Unknown",
>> +		"Unknown",
>> +		"Unknown",
>> +		"Unknown",
>> +		"Unknown",
>> +		"Unknown"
>> +	};
>> +
>> +	printf("%s: leaf:%s(%d)", header,
>> +		enclu_leaves[exinfop->leaf], exinfop->leaf);
>> +	if (exinfop->leaf != 4)
>> +		printf(" trap:%s(%d) ec:%d addr:0x%llx\n",
>> +			exception_names[exinfop->trapnr], exinfop->trapnr,
>> +			exinfop->error_code, exinfop->address);
>> +	else
>> +		printf("\n");
>> +}
>> +
>> +static const uint64_t MAGIC = 0x1122334455667788ULL;
>>   
>> -int main(int argc, char *argv[], char *envp[])
>> +static void test1(struct sgx_secs *secs)
> 
> test1, test2 and test3 are not too descriptive names. Every patch should
> make the code base cleaner, not messier.

I don't think it that important, as there are many test## occurrences in 
existing selftests. Anyway, I've added comments to those test functions 
to brief what is done. Hope you'll find them helpful.

> /Jarkko
>

Patch
diff mbox series

diff --git a/tools/testing/selftests/x86/sgx/Makefile b/tools/testing/selftests/x86/sgx/Makefile
index 3af15d7c8644..31f937e220c4 100644
--- a/tools/testing/selftests/x86/sgx/Makefile
+++ b/tools/testing/selftests/x86/sgx/Makefile
@@ -14,16 +14,16 @@  TEST_CUSTOM_PROGS := $(OUTPUT)/test_sgx
 all_64: $(TEST_CUSTOM_PROGS)
 
 $(TEST_CUSTOM_PROGS): main.c sgx_call.S $(OUTPUT)/encl_piggy.o
-	$(CC) $(HOST_CFLAGS) -o $@ $^
+	$(CC) $(HOST_CFLAGS) -o $@ $^ -lunwind -ldl -Wl,--defsym,__image_base=0 -pie
 
 $(OUTPUT)/encl_piggy.o: encl_piggy.S $(OUTPUT)/encl.bin $(OUTPUT)/encl.ss
 	$(CC) $(HOST_CFLAGS) -I$(OUTPUT) -c $< -o $@
 
 $(OUTPUT)/encl.bin: $(OUTPUT)/encl.elf
-	objcopy --remove-section=.got.plt -O binary $< $@
+	objcopy -O binary $< $@
 
 $(OUTPUT)/encl.elf: encl.lds encl.c encl_bootstrap.S
-	$(CC) $(ENCL_CFLAGS) -T $^ -o $@
+	$(CC) $(ENCL_CFLAGS) -T $^ -o $@ -Wl,--build-id=none
 
 $(OUTPUT)/encl.ss: $(OUTPUT)/sgxsign signing_key.pem $(OUTPUT)/encl.bin
 	$^ $@
diff --git a/tools/testing/selftests/x86/sgx/main.c b/tools/testing/selftests/x86/sgx/main.c
index e2265f841fb0..d3e53c71306d 100644
--- a/tools/testing/selftests/x86/sgx/main.c
+++ b/tools/testing/selftests/x86/sgx/main.c
@@ -1,6 +1,7 @@ 
 // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
 // Copyright(c) 2016-18 Intel Corporation.
 
+#define _GNU_SOURCE
 #include <elf.h>
 #include <fcntl.h>
 #include <stdbool.h>
@@ -9,16 +10,31 @@ 
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <errno.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
-#include <sys/time.h>
+#include <sys/auxv.h>
+#include <signal.h>
+#include <sys/ucontext.h>
+
+#define UNW_LOCAL_ONLY
+#include <libunwind.h>
+
 #include "encl_piggy.h"
 #include "defines.h"
 #include "../../../../../arch/x86/kernel/cpu/sgx/arch.h"
 #include "../../../../../arch/x86/include/uapi/asm/sgx.h"
 
-static const uint64_t MAGIC = 0x1122334455667788ULL;
+#define _Q(x)	__Q(x)
+#define __Q(x)	#x
+#define ERRLN	"Line " _Q(__LINE__)
+
+#define X86_EFLAGS_TF	(1ul << 8)
+
+extern char __image_base[];
+size_t eenter;
+static size_t vdso_base;
 
 struct vdso_symtab {
 	Elf64_Sym *elf_symtab;
@@ -26,20 +42,11 @@  struct vdso_symtab {
 	Elf64_Word *elf_hashtab;
 };
 
-static void *vdso_get_base_addr(char *envp[])
+static void vdso_init(void)
 {
-	Elf64_auxv_t *auxv;
-	int i;
-
-	for (i = 0; envp[i]; i++);
-	auxv = (Elf64_auxv_t *)&envp[i + 1];
-
-	for (i = 0; auxv[i].a_type != AT_NULL; i++) {
-		if (auxv[i].a_type == AT_SYSINFO_EHDR)
-			return (void *)auxv[i].a_un.a_val;
-	}
-
-	return NULL;
+	vdso_base = getauxval(AT_SYSINFO_EHDR);
+	if (!vdso_base)
+		exit(1);
 }
 
 static Elf64_Dyn *vdso_get_dyntab(void *addr)
@@ -66,8 +73,9 @@  static void *vdso_get_dyn(void *addr, Elf64_Dyn *dyntab, Elf64_Sxword tag)
 	return NULL;
 }
 
-static bool vdso_get_symtab(void *addr, struct vdso_symtab *symtab)
+static bool vdso_get_symtab(struct vdso_symtab *symtab)
 {
+	void *addr = (void *)vdso_base;
 	Elf64_Dyn *dyntab = vdso_get_dyntab(addr);
 
 	symtab->elf_symtab = vdso_get_dyn(addr, dyntab, DT_SYMTAB);
@@ -138,7 +146,7 @@  static bool encl_create(int dev_fd, unsigned long bin_size,
 	base = mmap(NULL, secs->size, PROT_READ | PROT_WRITE | PROT_EXEC,
 		    MAP_SHARED, dev_fd, 0);
 	if (base == MAP_FAILED) {
-		perror("mmap");
+		perror(ERRLN);
 		return false;
 	}
 
@@ -224,35 +232,271 @@  static bool encl_load(struct sgx_secs *secs, unsigned long bin_size)
 	return false;
 }
 
-void sgx_call(void *rdi, void *rsi, void *tcs,
-	      struct sgx_enclave_exception *exception,
-	      void *eenter);
+int sgx_call(void *rdi, void *rsi, long rdx, void *rcx, void *r8, void *r9,
+	     void *tcs, struct sgx_enclave_exinfo *ei, void *cb);
+
+static void show_enclave_exinfo(const struct sgx_enclave_exinfo *exinfop,
+				const char *header)
+{
+	static const char * const enclu_leaves[] = {
+		"EREPORT",
+		"EGETKEY",
+		"EENTER",
+		"ERESUME",
+		"EEXIT"
+	};
+	static const char * const exception_names[] = {
+		"#DE",
+		"#DB",
+		"NMI",
+		"#BP",
+		"#OF",
+		"#BR",
+		"#UD",
+		"#NM",
+		"#DF",
+		"CSO",
+		"#TS",
+		"#NP",
+		"#SS",
+		"#GP",
+		"#PF",
+		"Unknown",
+		"#MF",
+		"#AC",
+		"#MC",
+		"#XM",
+		"#VE",
+		"Unknown",
+		"Unknown",
+		"Unknown",
+		"Unknown",
+		"Unknown",
+		"Unknown",
+		"Unknown",
+		"Unknown",
+		"Unknown",
+		"Unknown",
+		"Unknown"
+	};
+
+	printf("%s: leaf:%s(%d)", header,
+		enclu_leaves[exinfop->leaf], exinfop->leaf);
+	if (exinfop->leaf != 4)
+		printf(" trap:%s(%d) ec:%d addr:0x%llx\n",
+			exception_names[exinfop->trapnr], exinfop->trapnr,
+			exinfop->error_code, exinfop->address);
+	else
+		printf("\n");
+}
+
+static const uint64_t MAGIC = 0x1122334455667788ULL;
 
-int main(int argc, char *argv[], char *envp[])
+static void test1(struct sgx_secs *secs)
+{
+	uint64_t result = 0;
+	struct sgx_enclave_exinfo exinfo;
+
+	printf("[1] Entering the enclave without callback.\n");
+
+	printf("Input: 0x%lx\n Expect: Same as input\n", MAGIC);
+	sgx_call((void *)&MAGIC, &result, 0, NULL, NULL, NULL,
+		 (void *)secs->base, &exinfo, NULL);
+	show_enclave_exinfo(&exinfo, " Exit");
+	if (result != MAGIC) {
+		fprintf(stderr, "0x%lx != 0x%lx\n", result, MAGIC);
+		exit(1);
+	}
+	printf(" Output: 0x%lx\n", result);
+
+	printf("Input: Null TCS\n Expect: #PF at EENTER\n");
+	sgx_call((void *)&MAGIC, &result, 0, NULL, NULL, NULL,
+		 NULL, &exinfo, NULL);
+	show_enclave_exinfo(&exinfo, " Exit");
+	if (exinfo.leaf != 2 /*EENTER*/ || exinfo.trapnr != 14 /*#PF*/)
+		exit(1);
+}
+
+static int test2_callback(long rdi, long rsi, long rdx,
+			  struct sgx_enclave_exinfo *ei, long r8, long r9,
+			  void *tcs, long ursp)
+{
+	show_enclave_exinfo(ei, "  callback");
+
+	switch (ei->leaf) {
+	case 4:
+		return 0;
+	case 3:
+	case 2:
+		switch (ei->trapnr) {
+		case 1:	/*#DB*/
+			break;
+		case 14:/*#PF*/
+			if ((ei->error_code & 1) == 0) {
+				fprintf(stderr, ERRLN
+					": Unexpected #PF error code\n");
+				exit(1);
+			}
+			if (mprotect((void *)(ei->address & -0x1000), 0x1000,
+				     ((ei->error_code & 2) ? PROT_WRITE : 0) |
+				     ((ei->error_code & 0x10) ? PROT_EXEC : 0) |
+				     PROT_READ)) {
+				perror(ERRLN);
+				exit(1);
+			}
+			break;
+		default:
+			fprintf(stderr, ERRLN ": Unexpected exception\n");
+			exit(1);
+		}
+		return ei->leaf == 2 ? -EAGAIN : ei->leaf;
+	}
+	return -EINVAL;
+}
+
+static void test2(struct sgx_secs *secs)
+{
+	uint64_t result = 0;
+	struct sgx_enclave_exinfo exinfo;
+
+	printf("[2] Entering the enclave with callback.\n");
+
+	printf("Input: 0x%lx\n Expect: Same as input\n", MAGIC);
+	sgx_call((void *)&MAGIC, &result, 0, NULL, NULL, NULL,
+		 (void *)secs->base, &exinfo, test2_callback);
+	if (result != MAGIC) {
+		fprintf(stderr, "0x%lx != 0x%lx\n", result, MAGIC);
+		exit(1);
+	}
+	printf(" Output: 0x%lx\n", result);
+
+	printf("Input: Read-only enclave (0x%lx-0x%lx)\n"
+	       " Expect: #PFs to be fixed by callback\n",
+	       secs->base, secs->base + (encl_bin_end - encl_bin) - 1);
+	if (mprotect((void *)secs->base, encl_bin_end - encl_bin, PROT_READ)) {
+		perror(ERRLN);
+		exit(1);
+	}
+	while (sgx_call((void *)&MAGIC, &result, 0, NULL, NULL, NULL,
+			(void *)secs->base, &exinfo, test2_callback) == -EAGAIN)
+		;
+	show_enclave_exinfo(&exinfo, " Exit");
+	if (exinfo.leaf != 4 /*EEXIT*/)
+		exit(1);
+}
+
+static struct test3_proc_context {
+	unw_word_t	ip, bx, sp, bp, r12, r13, r14, r15;
+} test3_ctx;
+
+static unw_word_t test3_getcontext(unw_cursor_t *cursor,
+				   struct test3_proc_context *ctxp)
+{
+	unw_get_reg(cursor, UNW_REG_IP, &ctxp->ip);
+	unw_get_reg(cursor, UNW_REG_SP, &ctxp->sp);
+	unw_get_reg(cursor, UNW_X86_64_RBX, &ctxp->bx);
+	unw_get_reg(cursor, UNW_X86_64_RBP, &ctxp->bp);
+	unw_get_reg(cursor, UNW_X86_64_R12, &ctxp->r12);
+	unw_get_reg(cursor, UNW_X86_64_R13, &ctxp->r13);
+	unw_get_reg(cursor, UNW_X86_64_R14, &ctxp->r14);
+	unw_get_reg(cursor, UNW_X86_64_R15, &ctxp->r15);
+	return ctxp->ip;
+}
+
+static void test3_sigtrap(int sig, siginfo_t *info, ucontext_t *ctxp)
+{
+	static int in_vdso_eenter;
+	static size_t caller;
+
+	unw_cursor_t cursor;
+	unw_context_t uc;
+	struct test3_proc_context pc;
+
+	if (ctxp->uc_mcontext.gregs[REG_RIP] == eenter) {
+		in_vdso_eenter = 1;
+		caller = *(size_t *)(ctxp->uc_mcontext.gregs[REG_RSP]);
+		printf("  trace started at ip:%llx (vdso:0x%llx)\n",
+			ctxp->uc_mcontext.gregs[REG_RIP],
+			ctxp->uc_mcontext.gregs[REG_RIP] - vdso_base);
+	}
+
+	if (!in_vdso_eenter)
+		return;
+
+	if (ctxp->uc_mcontext.gregs[REG_RIP] == caller) {
+		in_vdso_eenter = 0;
+		ctxp->uc_mcontext.gregs[REG_EFL] &= ~X86_EFLAGS_TF;
+		printf("  trace ended successfully at ip:%llx (executable:0x%llx)\n",
+			ctxp->uc_mcontext.gregs[REG_RIP],
+			ctxp->uc_mcontext.gregs[REG_RIP] -
+				(size_t)__image_base);
+		return;
+	}
+
+	unw_getcontext(&uc);
+	unw_init_local(&cursor, &uc);
+	while (unw_step(&cursor) > 0 &&
+	       test3_getcontext(&cursor, &pc) != test3_ctx.ip)
+		;
+
+	if (memcmp(&pc, &test3_ctx, sizeof(pc))) {
+		fprintf(stderr, ERRLN ": Error unwinding\n");
+		exit(1);
+	}
+}
+
+static void test3_set_tf(void)
+{
+	__asm__ ("pushfq; orl %0, (%%rsp); popfq" : : "i"(X86_EFLAGS_TF));
+}
+
+static void test3(struct sgx_secs *secs)
+{
+	unw_cursor_t cursor;
+	unw_context_t uc;
+	struct sigaction sa = {
+		.sa_sigaction = (void (*)(int, siginfo_t*, void*))test3_sigtrap,
+		.sa_flags = SA_SIGINFO,
+	};
+
+	unw_getcontext(&uc);
+	unw_init_local(&cursor, &uc);
+	if (unw_step(&cursor) > 0)
+		test3_getcontext(&cursor, &test3_ctx);
+	else {
+		fprintf(stderr, ERRLN ": error initializing unwind context\n");
+		exit(1);
+	}
+
+	if (sigaction(SIGTRAP, &sa, NULL) < 0) {
+		perror(ERRLN);
+		exit(1);
+	}
+
+	test3_set_tf();
+	test1(secs);
+
+	test3_set_tf();
+	test2(secs);
+}
+
+int main(void)
 {
 	unsigned long bin_size = encl_bin_end - encl_bin;
 	unsigned long ss_size = encl_ss_end - encl_ss;
-	struct sgx_enclave_exception exception;
 	Elf64_Sym *eenter_sym;
 	struct vdso_symtab symtab;
 	struct sgx_secs secs;
-	uint64_t result = 0;
-	void *eenter;
-	void *addr;
-
-	memset(&exception, 0, sizeof(exception));
 
-	addr = vdso_get_base_addr(envp);
-	if (!addr)
-		exit(1);
+	vdso_init();
 
-	if (!vdso_get_symtab(addr, &symtab))
+	if (!vdso_get_symtab(&symtab))
 		exit(1);
 
 	eenter_sym = vdso_symtab_get(&symtab, "__vdso_sgx_enter_enclave");
 	if (!eenter_sym)
 		exit(1);
-	eenter = addr + eenter_sym->st_value;
+	eenter = vdso_base + eenter_sym->st_value;
 
 	printf("Binary size %lu (0x%lx), SIGSTRUCT size %lu\n", bin_size,
 	       bin_size, ss_size);
@@ -266,14 +510,11 @@  int main(int argc, char *argv[], char *envp[])
 	if (!encl_load(&secs, bin_size))
 		exit(1);
 
-	printf("Input: 0x%lx\n", MAGIC);
-	sgx_call((void *)&MAGIC, &result, (void *)secs.base, &exception,
-		 eenter);
-	if (result != MAGIC) {
-		fprintf(stderr, "0x%lx != 0x%lx\n", result, MAGIC);
-		exit(1);
-	}
+	printf("--- Functional Tests ---\n");
+	test1(&secs);
+	test2(&secs);
 
-	printf("Output: 0x%lx\n", result);
-	exit(0);
+	printf("--- Unwind Tests ---\n");
+	test3(&secs);
+	return 0;
 }
diff --git a/tools/testing/selftests/x86/sgx/sgx_call.S b/tools/testing/selftests/x86/sgx/sgx_call.S
index 14bd0a044199..ca2c0c947758 100644
--- a/tools/testing/selftests/x86/sgx/sgx_call.S
+++ b/tools/testing/selftests/x86/sgx/sgx_call.S
@@ -7,9 +7,43 @@ 
 
 	.global sgx_call
 sgx_call:
+	.cfi_startproc
+	push	%r15
+	.cfi_adjust_cfa_offset	8
+	.cfi_rel_offset		%r15, 0
+	push	%r14
+	.cfi_adjust_cfa_offset	8
+	.cfi_rel_offset		%r14, 0
+	push	%r13
+	.cfi_adjust_cfa_offset	8
+	.cfi_rel_offset		%r13, 0
+	push	%r12
+	.cfi_adjust_cfa_offset	8
+	.cfi_rel_offset		%r12, 0
 	push	%rbx
-	mov	$0x02, %rax
-	mov	%rdx, %rbx
-	call	*%r8
+	.cfi_adjust_cfa_offset	8
+	.cfi_rel_offset		%rbx, 0
+	push	$0
+	.cfi_adjust_cfa_offset	8
+	push	0x48(%rsp)
+	.cfi_adjust_cfa_offset	8
+	push	0x48(%rsp)
+	.cfi_adjust_cfa_offset	8
+	push	0x48(%rsp)
+	.cfi_adjust_cfa_offset	8
+	mov	$2, %eax
+	call	*eenter(%rip)
+	add	$0x20, %rsp
+	.cfi_adjust_cfa_offset	-0x20
 	pop	%rbx
+	.cfi_adjust_cfa_offset	-8
+	pop	%r12
+	.cfi_adjust_cfa_offset	-8
+	pop	%r13
+	.cfi_adjust_cfa_offset	-8
+	pop	%r14
+	.cfi_adjust_cfa_offset	-8
+	pop	%r15
+	.cfi_adjust_cfa_offset	-8
 	ret
+	.cfi_endproc