Applications can be debugged by hooking into a debugger, by looking at a backtrace, or by using core files. In SGX, core files can't capture enclave memory and hence can't be used to debug the application running within an enclave.
Only debug mode enclaves can be inspected by a debugger.
In order to inspect a debug mode enclave, you need to first attach the debugger to the runner (that is
supply the debugger with information needed to correctly map the symbols in the enclave's ELF file to the enclave load address.
Once the debugger knows the correct mapping, you need to save the runner's context and switch to the enclave's context.
EDP has simplified these steps by extending GDB with a GDB python script.
Before we get into actual debugging, here are a few terms that will be referred to later:
elf_file is the ELF that will be generated by Rust compiler (generally invoked via
Refer to installation_guide for more details on Cargo command-line usage.
You can locate the ELF file from the "cargo run" or "cargo test" output, e.g.:
$ cargo run --target x86_64-fortanix-unknown-sgx Finished dev [unoptimized + debuginfo] target(s) in 0.03s Running `ftxsgx-runner-cargo target/x86_64-fortanix-unknown-sgx/debug/sgxtest` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The SGXS file is a binary in the processor's native SGXS format which will be loaded by the runner while creating the enclave.
The SGXS file is generated by converting the elf_file. This is normally done automatically when doing
cargo run, but you can refer to the deployment gudie for details of the conversion process.
cargo build do not forget to convert an updated build before inspecting it via a debugger.
thread control structure (TCS)
The TCS represents the context of a thread running in the enclave. The context includes the register states, stack information, etc.
In case of an interrupt or asynchronous exit (AEX), the register state will be stored in the state save area (SSA) for the current TCS.
sgxstate command from the EDP GDB script interprets the TCS and related state to reconstruct the enclave context.
Currently, GDB does not support single stepping SGX enclaves. Enclave aborts and processor exceptions can be debugged using
gdb.py with the following steps:
Download gdb.py, if not yet downloaded.
Start runner via GDB:
$ gdb --args ftxsgx-runner <path_to_sgxs_file>
You can also attach GDB to a running
(gdb) source <path_to_gdb.py>
You might want to add the above line in an appropriate
.gdbinitto avoid repeating this step each time.
Run until the enclave hits an abort or a processor exception:
At this point you might see messages that say the program received a SIGTRAP. This is because upon enclave abort or processor exception, the runner sets the TCS address in
SIGTRAP, shuttling control back to GDB.
Map enclave symbols and switch to enclave state:
(gdb) sgxstate auto <path_to_elf_file>
Now you can use standard GDB commands to inspect the enclave stack.
To restore the state back to the runner, use:
(gdb) sgxstate restore
To switch back to the same thread within the enclave, use:
(gdb) sgxstate auto
This time you do not need not specify the
elf_fileto generate mapping. Be sure that you are in the topmost frame of the runner stack, since
sgxstate autoreads the
rbxregister to get the TCS address. Or, if the current frame does not have TCS in
rbx, but you know the TCS address, you can use below command to manually switch state:
(gdb) sgxstate tcs <TCS ADDR>
By default, the stack is completely unwound after a panic before control reaches the runner, so the debugger can't inspect the panic. In
Cargo.tomlyou can configure panics to abort rather than unwind as follows, to preserve the call stack for the debugger:
[profile.dev] panic = "abort"
As with most of the Rust binaries, panicking will cause a backtrace to be printed on the console if the environment variable
RUST_BACKTRACE is set to
Since there is no way to inject environment variables from userspace, you need to add code to set the environment variable as follows:
To reduce size of the trusted code base, EDP does not resolve symbols in the backtrace, so the backtrace from an enclave will only print the offsets of the stack address. You can use stack-trace-resolve to resolve the symbols automatically.
A typical usage of
$ cargo +nightly run --target=x86_64-fortanix-unknown-sgx | scripts/stack-trace-resolve -e target/x86_64-fortanix-unknown-sgx/debug/<elf_file>
You can use
stack-trace-resolve -h or refer to stack-trace-resolve info for usage of the script.
You can also use
addr2line to manually resolve the symbols.
Technically, in the SGX threat model the attacker already knows the return addresses in your stack frames, but to make things harder for the attacker, you might not want to set
RUST_BACKTRACE = 1 for release builds. Also, you might want to check that the panic message for release builds does not reveal more information than it should.