mirror of
https://github.com/clearlinux/graphene.git
synced 2026-07-31 17:17:01 +00:00
5ef9bdc861
- Use the same mechanism (debug_map) in Pal/Linux and Pal/Linux-SGX. Previously, Pal/Linux emulated the _r_debug structure, normally maintained by ld.so, but that cannot be done in SGX outer PAL, because it's loaded by ld.so already. - Maintain the debug maps outside of SGX enclave. This allows initializing them before enclave start, and potentially makes them easier to use. - Initialize PAL debug map before enclave start. Previously, this was done from inside the enclave, so you couldn't set a breakpoint too early (e.g. in pal_linux_main). - Store only load address, without list of sections. This is to avoid parsing the list of sections just to report them to the debugger. Unfortunately, the GDB version that we support still needs these sections, but we can retrieve them in GDB plugin. - Move Python GDB code related to debug maps to a common file.
138 lines
4.9 KiB
Python
138 lines
4.9 KiB
Python
import contextlib
|
|
import os
|
|
import pathlib
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import unittest
|
|
|
|
# pylint: disable=subprocess-popen-preexec-fn,subprocess-run-check
|
|
|
|
HAS_SGX = os.environ.get('SGX') == '1'
|
|
ON_X86 = os.uname().machine in ['x86_64']
|
|
|
|
def expectedFailureIf(predicate):
|
|
if predicate:
|
|
return unittest.expectedFailure
|
|
return lambda func: func
|
|
|
|
class RegressionTestCase(unittest.TestCase):
|
|
LOADER_ENV = 'PAL_LOADER'
|
|
LIBPAL_PATH_ENV = 'LIBPAL_PATH'
|
|
HOST_PAL_PATH_ENV = 'HOST_PAL_PATH'
|
|
DEFAULT_TIMEOUT = (20 if HAS_SGX else 10)
|
|
|
|
def get_env(self, name):
|
|
try:
|
|
return os.environ[name]
|
|
except KeyError:
|
|
self.fail('environment variable {} not set'.format(name))
|
|
|
|
def has_debug(self):
|
|
libpal = self.get_env(self.LIBPAL_PATH_ENV)
|
|
p = subprocess.run(['objdump', '-x', libpal], check=True, stdout=subprocess.PIPE)
|
|
dump = p.stdout.decode()
|
|
return '.debug_info' in dump
|
|
|
|
def run_gdb(self, args, gdb_script, **kwds):
|
|
host_pal_path = self.get_env(self.HOST_PAL_PATH_ENV)
|
|
|
|
# See also pal_loader.
|
|
prefix = ['gdb', '-q']
|
|
env = os.environ.copy()
|
|
prefix += ['-x', os.path.join(host_pal_path, 'gdb_integration/debug_map_gdb.py')]
|
|
if HAS_SGX:
|
|
prefix += ['-x', os.path.join(host_pal_path, 'gdb_integration/graphene_sgx_gdb.py')]
|
|
sgx_gdb = os.path.join(host_pal_path, 'gdb_integration/sgx_gdb.so')
|
|
env['LD_PRELOAD'] = sgx_gdb + ':' + env.get('LD_PRELOAD', '')
|
|
else:
|
|
prefix += ['-x', os.path.join(host_pal_path, 'gdb_integration/graphene_gdb.py')]
|
|
|
|
# Override TTY, as apparently os.setpgrp() confuses GDB and causes it to hang.
|
|
prefix += ['-x', gdb_script, '-batch', '-tty=/dev/null']
|
|
prefix += ['--args']
|
|
|
|
return self.run_binary(args, prefix=prefix, env=env, **kwds)
|
|
|
|
def run_binary(self, args, *, timeout=None, prefix=None, **kwds):
|
|
timeout = (max(self.DEFAULT_TIMEOUT, timeout) if timeout is not None
|
|
else self.DEFAULT_TIMEOUT)
|
|
|
|
loader = self.get_env(self.LOADER_ENV)
|
|
if not pathlib.Path(loader).exists():
|
|
self.skipTest('loader ({}) not found'.format(loader))
|
|
|
|
libpal = self.get_env(self.LIBPAL_PATH_ENV)
|
|
if not pathlib.Path(libpal).exists():
|
|
self.skipTest('libpal ({}) not found'.format(libpal))
|
|
|
|
if prefix is None:
|
|
prefix = []
|
|
|
|
with subprocess.Popen([*prefix, loader, libpal, 'init', *args],
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
preexec_fn=os.setpgrp,
|
|
**kwds) as process:
|
|
try:
|
|
stdout, stderr = process.communicate(timeout=timeout)
|
|
except subprocess.TimeoutExpired:
|
|
os.killpg(process.pid, signal.SIGKILL)
|
|
self.fail('timeout ({} s) expired'.format(timeout))
|
|
|
|
self.print_output(stdout, stderr)
|
|
|
|
if process.returncode:
|
|
raise subprocess.CalledProcessError(
|
|
process.returncode, args, stdout, stderr)
|
|
|
|
return stdout.decode(), stderr.decode()
|
|
|
|
@classmethod
|
|
def run_native_binary(cls, args, timeout=None, libpath=None, **kwds):
|
|
timeout = (max(cls.DEFAULT_TIMEOUT, timeout) if timeout is not None
|
|
else cls.DEFAULT_TIMEOUT)
|
|
|
|
my_env = os.environ.copy()
|
|
if not libpath is None:
|
|
my_env["LD_LIBRARY_PATH"] = libpath
|
|
|
|
with subprocess.Popen(args,
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
env=my_env,
|
|
preexec_fn=os.setpgrp,
|
|
**kwds) as process:
|
|
try:
|
|
stdout, stderr = process.communicate(timeout=timeout)
|
|
except subprocess.TimeoutExpired:
|
|
os.killpg(process.pid, signal.SIGKILL)
|
|
raise AssertionError('timeout ({} s) expired'.format(timeout))
|
|
|
|
cls.print_output(stdout, stderr)
|
|
|
|
if process.returncode:
|
|
raise subprocess.CalledProcessError(
|
|
process.returncode, args, stdout, stderr)
|
|
|
|
return stdout.decode(), stderr.decode()
|
|
|
|
@staticmethod
|
|
def print_output(stdout: bytes, stderr: bytes):
|
|
'''
|
|
Print command output (stdout, stderr) so that pytest can capture it.
|
|
'''
|
|
|
|
sys.stdout.write(stdout.decode(errors='surrogateescape'))
|
|
sys.stderr.write(stderr.decode(errors='surrogateescape'))
|
|
|
|
@contextlib.contextmanager
|
|
def expect_returncode(self, returncode):
|
|
if returncode == 0:
|
|
raise ValueError('expected returncode should be nonzero')
|
|
try:
|
|
yield
|
|
self.fail('did not fail (expected {})'.format(returncode))
|
|
except subprocess.CalledProcessError as e:
|
|
self.assertEqual(e.returncode, returncode,
|
|
'failed with returncode {} (expected {})'.format(
|
|
e.returncode, returncode))
|