Files
graphene/Scripts/regression.py
T
Paweł Marczewski 5ef9bdc861 [Pal] Unify debug maps
- 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.
2021-01-18 03:49:18 +01:00

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))